useMemo 是 React 提供的一个 Hook,用于在函数组件中进行性能优化,避免不必要的计算和渲染。
在 React 中,组件的渲染是由组件的 props 和状态(state)决定的。当组件的 props 或状态发生变化时,组件会重新渲染。而有时候,某些计算操作可能是比较耗时的,如果在每次渲染时都执行这些计算,会导致性能下降。
这时,可以使用 useMemo 来优化性能。useMemo 接收一个计算函数和一个依赖数组,它会记住计算函数的返回值,并且只在依赖数组发生变化时重新计算。当依赖数组不变时,会直接返回上一次缓存的值,避免重复计算。
useMemo 的使用步骤如下:
function calculateExpensiveValue(a, b) {
// 执行耗时的计算操作
// ...
return result;
}
function MyComponent() {
const a = 5;
const b = 10;
const result = useMemo(() => calculateExpensiveValue(a, b), [a, b]);
return <div>{result}</div>;
}
在上面的例子中,当 a 或 b 发生变化时,useMemo 会执行计算函数 calculateExpensiveValue,并将结果缓存起来。如果 a 和 b 的值在下一次渲染时没有发生变化,useMemo 会直接返回上一次缓存的结果。
需要注意的是,依赖数组是可选的。如果不传入依赖数组,那么 useMemo 的计算函数将在每次渲染时都执行。传入空数组([])作为依赖数组,可以使 useMemo 只在组件的初始渲染时执行一次。
总结来说,useMemo 是 React 提供的一个用于性能优化的 Hook,它会记住计算函数的返回值,并且只在依赖数组发生变化时重新计算。通过使用 useMemo,我们可以避免不必要的计算和渲染,提升应用的性能。
虽然 useMemo 和 useCallback 都是 React 提供的用于性能优化的 Hook,但它们在使用场景和作用上有一些区别。
useMemo 的主要作用是缓存一个计算结果,并在依赖项发生变化时重新计算。它接收一个计算函数和一个依赖数组作为参数,并返回计算函数的结果。在组件重新渲染时,如果依赖项未发生变化,则 useMemo 会直接返回上一次缓存的结果,避免重复计算。适合用于缓存昂贵的计算操作的结果。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback 的主要作用是缓存一个函数,以便在子组件中使用时,子组件不会重新创建该函数。它接收一个函数和一个依赖数组作为参数,并返回一个记忆化后的函数。在依赖项未发生变化的情况下,useCallback 会返回同一个函数实例,避免了不必要的函数创建和传递给子组件。适合用于将回调函数传递给子组件时,避免子组件在每次渲染时都创建新的回调函数。
const memoizedCallback = useCallback(() => {
// do something
}, [a, b]);
总结区别:
useMemo 缓存一个计算结果,适合用于缓存昂贵的计算操作的结果。
useCallback 缓存一个函数,适合用于避免子组件在每次渲染时都创建新的回调函数。
需要注意的是,虽然 useMemo 和 useCallback 都可以在依赖项未发生变化时避免重新计算,但它们并不能完全代替彼此。具体使用哪个 Hook 取决于具体的优化需求和场景。
自定义 Hook 是一种在 React 中共享逻辑的机制。它可以让你将组件逻辑提取到可重用的函数中,并在函数组件中使用。
自定义 Hook 按照一定的命名规则命名,以 “use” 开头,例如 useFetchData 或 useTheme。它们可以使用其他的 React Hook,也可以是纯 JavaScript 函数。
自定义 Hook 的步骤如下:
function useFetchData(url) {
// ...具体的逻辑
}
function useFetchData(url) {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch(url);
const data = await response.json();
setData(data);
};
fetchData();
}, [url]);
return data;
}
function MyComponent() {
const data = useFetchData("https://api.example.com/data");
return (
<div>
{data ? (
<ul>
{data.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
) : (
<div>Loading...</div>
)}
</div>
);
}
通过上述步骤,我们可以将一些通用的逻辑封装到自定义 Hook 中,并在需要的地方重用它。这样可以提高代码的重用性和可维护性,使组件更加关注视图层的展示。
需要注意的是,自定义 Hook 只是一种约定,它并不会引入额外的 React 渲染或状态管理的机制。它仅仅是一种将逻辑提取到可重用函数中的方式。
使用 shouldComponentUpdate 避免不需要的渲染,但是如果对 props 和 state 做深比较,代价很大,所以需要根据业务进行些取舍;在有子组件的情况下,为了避免子组件的重复渲染,可以通过父组件来判断子组件是否需要 PureRender。
将 props 设置为数组或对象:每次调用 React 组件都会创建新组件,就算传入的数组或对象的值没有改变,他们的引用地址也会发生改变,比如,如果按照如下的写法,那么每次渲染时 style 都是一个新对象
// 不推荐
<button style={{ color: 'red' }} />
// 推荐
const style = { color: 'red' }
<button style={style} />
// 不推荐
<button style={this.props.style || {} } />
// 推荐
const defaultStyle = {}
<button style={this.props.style || defaultStyle } />
// 使用 concat() 方法创建新的不可变数组
const newArray = oldArray.concat(newValue);
// 使用 Object.assign() 方法创建新的不可变对象
const newObject = Object.assign({}, oldObject, { key: newValue });
import { Map, List } from 'immutable';
// 使用 Immutable.js 创建新的不可变 Map
const newMap = Map({ key: value });
// 使用 Immer 创建新的不可变对象
const newObject = produce(oldObject, (draft) => {
draft.key = newValue;
});
// 使用 useState Hook 更新不可变数组
const [array, setArray] = useState([]);
const addToArray = (newValue) => {
setArray((prevArray) => [...prevArray, newValue]);
};
// 使用 useReducer Hook 更新不可变对象
const initialState = { key: value };
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE':
return { ...state, key: action.payload };
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, initialState);
const updateState = (newValue) => {
dispatch({ type: 'UPDATE', payload: newValue });
};
使用不可变数据的好处是,它可以帮助我们避免直接修改原始数据,从而避免出现不必要的副作用和错误。不可变数据也有助于优化 React 的性能,因为它可以帮助 React 更准确地进行 Virtual DOM 的比较和渲染。
在 useEffect 中直接使用 async/await 是不支持的,因为 useEffect 函数必须返回一个清除函数或者是 undefined。
然而,你可以创建一个内部的异步函数,并在 useEffect 中调用这个函数来实现类似的效果。下面是一个使用 async/await 的示例:
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// 处理数据
} catch (error) {
// 处理错误
}
};
fetchData();
}, []);
在上面的例子中,我们定义了一个内部的异步函数 fetchData,并在 useEffect 中调用它。这样可以使用 async/await 来处理异步操作,同时仍然符合 useEffect 的要求。
需要注意的是,由于 fetchData 是在 useEffect 中定义的,它可以访问 useEffect 中的变量和 props。如果你的异步操作依赖于 useEffect 中的某些值,请确保将这些值添加到依赖数组中,以便在它们发生变化时重新运行 useEffect。
另外,如果你希望在组件卸载时取消异步操作,可以在 fetchData 函数中添加取消逻辑,并在清除函数中调用它。
总结来说,虽然 useEffect 不直接支持 async/await,但你可以通过在 useEffect 中调用内部的异步函数来实现类似的效果。这样可以使用 async/await 来处理异步操作,并在组件挂载和卸载时进行清理。
import { useEffect } from 'react';
function useAsyncEffect(effect, dependencies) {
useEffect(() => {
const cleanupFunction = effect();
return () => {
if (typeof cleanupFunction === 'function') {
cleanupFunction();
}
};
}, dependencies);
}
export default useAsyncEffect;
在上述代码中,我们定义了一个名为 useAsyncEffect 的自定义 Hook。它接收一个异步函数 effect 和一个依赖数组 dependencies,并在 useEffect 中执行这个异步函数。
使用这个自定义 Hook 的示例:
import useAsyncEffect from './useAsyncEffect';
function MyComponent() {
useAsyncEffect(async () => {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// 处理数据
} catch (error) {
// 处理错误
}
}, []);
// 组件的其他逻辑
return <div>...</div>;
}
在这个示例中,我们使用了自定义 Hook useAsyncEffect,将异步操作的逻辑封装到了其中。这样,我们可以在函数组件中像使用普通的 useEffect 一样使用这个支持异步的效果。
需要注意的是,自定义 Hook useAsyncEffect 内部仍然是使用了 useEffect,因此它也遵循 useEffect 的规则和限制。在使用自定义 Hook 时,也需要将需要的值添加到依赖数组 dependencies 中,以确保在这些依赖发生变化时重新运行异步函数。
总结来说,通过封装一个自定义 Hook,我们可以实现支持异步操作的 useEffect。这样可以更加方便地在函数组件中处理异步逻辑,同时符合 React Hook 的规范和要求。
在类组件中,可以用this.state来定义类组件的状态,可以看下以下代码实现
import React from 'react'
class StateClass extends React.Component{
constructor(){
super()
this.state = {
name: '类'
}
}
render() {
return (
<div onClick={ this.setName }>
这是一个类组件————{ this.state.name }
</div>
)
}
setName = () => {
this.setState({
name: '我通过类组件方法变成这样了'
})
}
}
export default StateClass
在函数组件中,可以使用useState来定义函数组件的状态。使用useState来创建状态
import React,{ useState } from 'react'
function StateFunction () {
const [name, setName] = useState('函数')
// 类名,修改函数名 初始值
return (
<div onClick={ () => setName('我使用hooks变成这样了') }>
// setName也可以写入方法,如setName( val => val+'xxxx' )
这是一个函数式组件————{name}
</div>
)
}
export default StateFunction
useEffect又称副作用hooks。作用:给没有生命周期的组件,添加结束渲染的信号。执行时机:在渲染结束之后执行
如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数,所以你要考虑好使用场景。
import React,{ useEffect, useState } from 'react'
function StateFunction () {
const [num, setNum] = useState(0)
useEffect( () => {
console.log('2222函数式组件结束渲染')
})
return (
<div onClick={ () => setNum( num => num+1 ) }>
这是一个函数式组件————{num}
</div>
)
}
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[])
// 改变useEffect第二个参数,其余代码同上
在这,我们可以对第二个参数传入一个数组,这个数组表示的是更新执行所依赖的列表,只有依赖列表改变时(当数组中的任意一项变化的时候,useEffect会被重新执行 ),才会触发回调函数
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[num])
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[num,val])
// 改变useEffect第二个参数,其余代码同上
上面写的都是一些不需要清除的副作用,只是回调触发一些简单的方法,但是有一些副作用是需要清除的。例如绑定一些DOM事件,在这种情况下,清除工作是非常重要的,可以防止引起内存泄露,例如下面给出的代码对比
const [positions,setPositions] = useState({ x:0,y:0 })
useEffect( () => {
console.log('2222函数式组件结束渲染')
const updateMouse = (e) => {
console.log('打印当前位置')
setPositions({ x:e.clientX, y:e.clientY })
}
document.addEventListener('click',updateMouse)
})
return (
<div>
<p>x:{ positions.x }</p>
<p>y:{ positions.y }</p>
</div>
)
useEffect( () => {
console.log('2222函数式组件结束渲染')
const updateMouse = (e) => {
console.log('打印当前位置')
setPositions({ x:e.clientX, y:e.clientY })
}
document.addEventListener('click',updateMouse) // 添加绑定方法事件(要修改依赖,绑定到依赖上)
return () => {
// 在每次执行useEffect之前都会执行上一次return中内容
document.removeEventListener('click',updateMouse)
// 移除绑定方法事件(要修改依赖,绑定到依赖上)
console.log('1111销毁')
}
})
每个effect函数都属于一次特定的渲染:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
console.log(`${count}`);
}, 3000);
});
return (
<div>
<p>你点击了{count}次</p>
<button onClick={() => setCount(count + 1)}>
点击我
</button>
</div>
);
}
this.state = {
num:0
}
componentDidMount(){
setTimeout(() => {
console.log(this.state.num)
},3000)
}
componentDidUpdate(){
setTimeout(() => {
console.log(this.state.num)
},3000)
}
render() {
return (
<div onClick={ this.setNum }>
这是一个类组件————{ this.state.num }
</div>
)
}
setNum = () => {
this.setState({
num: this.state.num+1
})
}
一般将useLayoutEffect称为有DOM操作的副作用hooks。作用是在DOM更新完成之后执行某个操作。执行时机:在DOM更新之后执行
与useEffect对比
1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回一个函数,先执行返回函数,再执行参数函数
(所以说执行过程的流程是一样的)
执行时机不同。useLayoutEffect在DOM更新之后执行;useEffect在render渲染结束后执行。执行示例代码会发现useLayoutEffect永远比useEffect先执行,这是因为DOM更新之后,渲染才结束或者渲染还会结束
const [num, setNum] = useState(0)
//在类组件中用componentWillMount生命周期来实现
useLayoutEffect( () => {
console.log('useLayoutEfffect')
// 也可以在此进行事件绑定
return () => {
// 也可以在此进行事件绑定移除
console.log(1)
}
},[num])
useEffect( () => {
console.log('useEffect')
},[num])
return (
<div onClick={ () => setNum( num => num+1 ) }>
这是一个函数式组件————{num}
</div>
)
使用useMemo可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用是让组件中的函数跟随状态更新(即优化函数组件中的功能函数)。
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
function getDoubleNum () {
console.log(`获取双倍Num${num}`)
return 2 * num // 假设为复杂计算逻辑
}
return (
<div onClick={ () => { setAge( age => age+1 )} }>
<br></br>
这是一个函数式组件————{ getDoubleNum() }
<br></br>
age的值为————{ age }
<br></br>
</div>
)
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
const getDoubleNum = useMemo( () => {
console.log(`获取双倍Num${num}`)
return 2 * num // 假设为复杂计算逻辑
},[num] )
return (
<div onClick={ () => { setAge( age => age+1 ) } }>
<br></br>
这是一个函数式组件————num:{ getDoubleNum } // 注意这里没括号,因为是返回值
<br></br>
age的值为————{ age }
<br></br>
</div>
)
const Child = memo( () => {
console.log('我是子组件')
return <p>我是子组件</p>
})
function Parent() {
const [show,setShow] = useState(true)
const info = {
name: 'Even',
age: 22
}
return(
<div>
<Child info={ info } />
<button onClick={ () => setShow(!show) }>点击更新状态</button>
</div>
)
}
const info = useMemo( () => {
return {
name: 'Even',
age: 22
}
},[])
useMemo讲完我们来讲一个跟其很相似的叫useCallback,作用也是让某些操作、方法跟随状态的更新而去执行。
与useMemo对比。
可以简单这样看作,useMemo(() => Fn,deps)相当于useCallback(Fn,deps)
不同点:
useCallback是对传过来的回调函数优化,返回的是一个函数;useMemo返回值可以是任何,函数,对象等都可以
相同点:
在使用方法上,useMemo与useCallback相同。接收一个函数作为参数,也同样接收第二个参数作为依赖列表
useCallback虽然与useMemo相似,但其返回及缓存的是一个函数,对比以下示例代码。先说①②③三种情况的对比(可以复制代码然后分别注释①②③代码对比)
const set = new Set()
export default function StateFunction () {
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
const getDoubleNum = useMemo( () => {
console.log(`获取双倍Num${num}`)
return 2 * num // ①假设为复杂计算逻辑
},[] )
const getDoubleNum = useCallback( () => {
console.log(`获取双倍Num${num}`)
return 2 * num // ②假设为复杂计算逻辑
},[] )
set.add(getDoubleNum()) // ③注意set打印的长度变化(设置Callback的依赖为[]、[num]进行对比)
console.log('set.size:',set.size)
return (
<div onClick={ () => { setNum( num => num+1 ) } }>
<br></br>
这是一个函数式组件————num:{ getDoubleNum } //①useMemo情况下
这是一个函数式组件————num:{ getDoubleNum() } //②useCallback情况下
<br></br>
age的值为————{ age }
<br></br>
</div>
)
}
可以对父子组件传参渲染的问题进行优化。简单来说就是,父组件的传入函数不更新,就不会触发子组件的函数重新执行
function Parent () {
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
const getDoubleNum = useCallback( () => {
console.log(`获取双倍Num${num}`)
return 2 * num
},[num] )
return (
<div onClick={ () => {setNum( num => num+1 )} }>
这是一个函数式组件————num:{ getDoubleNum() }
<br></br>
age的值为————age:{ age }
<br></br>
set.size:{set.size}
<Child callback={ getDoubleNum() }></Child>
</div>
)
}
function Child(props) {
useEffect( () => {
console.log('callback更新了') //这里代表的是需要跟随传入内容的改变而同步进行的操作
},[props.callback])
return (
<div>
子组件的getDoubleNum{props.callback}
</div>
)
}
简单总结使用场景判断:
简单来说useRef就是返回一个子元素索引,此索引在整个生命周期中保持不变。作用也就是:长久保存数据。注意事项,保存的对象发生改变,不通知。属性变更不会重新渲染
const [num, setNum] = useState(0)
let timer
useEffect( () => {
timer = setInterval( () => {
setNum( num => num+1 )
},400 )
},[] )
useEffect( () => {
if(num > 10){
console.log('大于10了,清除定时器')
console.log('timer:',timer)
// 因为每一个timer都是独立render的,所以获取不到
clearTimeout(timer)
}
},[num] )
return (
<div>
这是一个函数式组件————num:{ num }
</div>
)
使用useRef后,代码如下。我们可以看到num自增到11后就打印了一次大于10了,清除定时器以及ref.current 1,然后就停止自增了,因为定时器被清除了。ref是一个对象,ref.current存储了该定时器在整个生命周期中的id值,所以当清除定时器的时候,可以准确清除这个定时器.
保存一个值,在整个生命周期中维持不变
const [num, setNum] = useState(0)
const ref = useRef()
useEffect( () => {
ref.current = setInterval( () => {
setNum( num => num+1 )
},400 )
// ref.current = '111'
},[] )
useEffect( () => {
if(num > 10){
console.log('大于10了,清除定时器')
console.log('ref.current',ref.current)
clearTimeout(ref.current)
}
},[num] )
return (
<div>
这是一个函数式组件————num:{ num }
</div>
)
const [num, setNum] = useState(0)
const ref = useRef()
useEffect( () => {
ref.current = '111'
console.log('ref.current',ref.current)
},[] )
return (
<div>
这是ref.current的值——ref.current:{ ref.current }
<br></br>
这是一个函数式组件————num:{ num }
</div>
)
useContext是让子组件之间共享父组件传入的状态的。作用通俗地说是带着子组件去流浪。
function StateFunction () {
const [num, setNum] = useState(1)
return (
<div>
<button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button>
<br></br>
这是一个函数式组件——num:{ num }
<Item1 num={num}></Item1>
<Item2 num={num}></Item2>
// ......
</div>
)
}
function Item1 (props) {
return (
<div>
子组件1 num:{ props.num }
</div>
)
}
function Item2 (props) {
return (
<div>
子组件2 num:{ props.num }
</div>
)
}
const Context = createContext(null)
function StateFunction () {
const [num, setNum] = useState(1)
return (
<div>
<button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button>
<br></br>
这是一个函数式组件——num:{ num }
<Context.Provider value={num}>
<Item3></Item3>
<Item4></Item4>
</Context.Provider>
</div>
)
}
function Item3 () {
const num = useContext(Context)
return (
<div>
子组件3: { num }
</div>
)
}
function Item4 () {
const num = useContext(Context)
return (
<div>
子组件4: { num+2 }
</div>
)
}
以前是只能在类组件中使用Redux,现在我们可以通过useReducer在函数式组件中使用Redux。作用是可以从状态管理的工具中获取到想要的状态。
const store = {
age:18,
num:1
} // 数据仓库
const reducer = (state, action) => {
switch(action.type){
case 'add':
return {
...state,
num: action.num+1
}
default:
return {
...state
}
}
} // 管理者
function StateFunction () {
const [state,dispacth] = useReducer(reducer,store) // ①
return (
<div>
<button onClick={ () => {
dispacth({
type: 'add',
num: state.num
})
} }>
增加num的值+1
</button>
<br></br>
这是一个函数式组件——num:{ state.num }
</div>
)
}