memo、useMemo、useCallback 应用及区别,性能优化
当用class组件的时候我们可以通过 PureComponent 或者生命周期中的 shouldComponentUpdate 方法来进行优化,但是对于hooks要怎么做呢?
memo
React.memo 为高阶组件。它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。
如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
- MemoCustomCom 会检查 props 是否变化才会重新渲染子组件
- props (外部常量、或者通过 useState定义)的值改不会导致重新渲染组件
- 如果props值是在函数内容定义的值为引用类型类型,非基本数据类型,就会导致重新渲染组件
import React, { memo, useState } from 'react';
const types = [
{
id: 1,
name: 'react.js'
},
{
id: 2,
name: 'vue.js'
}
]
export default function Performance(props) {
const [ count, setCount ] = useState(0);
const [ a2, setA2 ] = useState('hello A');
const [ obj2, setObj2 ] = useState({
a: 1,
b: 2
});
const [ arr2, setArr2 ] = useState([1, 2, 3, 4]);
const add = () => {
setCount(count + 1);
}
const a = 'hello A';
const obj = {
a: 1,
b: 2
}
const arr = [1, 2, 3, 4]
return (
<div>
<p>hello Performance!</p>
<p style={{fontWeight: 'bold'}}>{count}</p>
<button onClick={add}>点击</button>
<p>子组件</p>
<CustomCom />
<p>memo组件</p>
<MemoCustomCom value={arr2} />
</div>
)
}
function CustomCom(props) {
console.log('渲染');
const currentTime = Date.now();
return (
<div>
{currentTime}
</div>
)
}
const MemoCustomCom = memo(CustomCom);
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。
默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。
function areEqual(prevProps, nextProps) {
return true;
}
const MemoCustomCom = memo(CustomCom, areEqual);
areEqual 这是不是引 class 组件的 shouldComponentUpdate 一样了
useMemo与useCallback区别
useCallback 可以理解为 useMemo的语法糖。 useCallback((x) => { log(x) }, [m]) 等价于 useMemo(() => { (x) => { log(x) } }, [m])
主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。
useCallBack | useMemo | |
返回值 | 一个缓存的回调函数 | 一个缓存的值 |
参数 | 需要缓存的函数,依赖项 | 需要缓存的值(也可以是个计算然后再返回值的函数) ,依赖项 |
使用场景 | 父组件更新时,通过props传递给子组件的函数也会重新创建,然后这个时候使用 useCallBack 就可以缓存函数不使它重新创建 | 组件更新时,一些计算量很大的值也有可能被重新计算,这个时候就可以使用 useMemo 直接使用上一次缓存的值 |
- 当父组件 value 值变化时,没有 useMemo 包裹的方法总是会执行
- 当父组件 value 值变化时,没有 useCallback 包裹的方法也问题会执行
import React, { useState, useMemo, useCallback, memo } from 'react';
export default function Performance2(props) {
const [ count, setCount ] = useState(0);
const [ value, setValue ] = useState(10);
const getSum = () => {
console.log('总计执行了');
return count;
}
const getSum2 = useMemo(() => {
console.log('useMemo 总计执行了');
return count;
}, [count])
const getSum3 = useCallback(() => {
console.log('useCallback 总计执行了');
return count;
}, [count])
return (
<div>
<p>hello performance!</p>
<p>{count}</p>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<button onClick={() => setCount(count + 1)}>加</button>
<p>数量:{getSum()}</p>
<p>数量:{getSum2}</p>
<p>数量:{getSum3()}</p>
<MemoChild getSum={getSum3} />
</div>
)
}
function Child(props) {
console.log('child 渲染了');
return (
<div>{props.getSum()}</div>
)
}
const MemoChild = memo(Child);
也许这里你发现的 useCallback 示例使用了 memo 包裹组件。是的,你没有看错,因为 useCallback 只缓存的函数,并不代表组件不会重新渲染,他们一般配合使用。
UseCallBack的应用场景
1、将回调函数作为props传递给子组件,可以使用UseCallBack来避免子组件不必要的重新渲染;
2、在使用useEffect时,如果依赖项数组中包含回调函数,可以使用UseCallBack来避免不必要的副作用执行;
3、在使用自定义Hook时,可以使用UseCallBack来优化自定义Hook的回调函数;
总结
- React.memo 认定两次地址是相同就可以避免子组件冗余的更新
- useMemo 针对不必要的计算进行优化,避免了当前组件中一些的冗余计算操作
- useCallBack 针对可能重新创建的函数进行优化,使得函数被缓存