何时使用 useCallback 与 useMemo
前言
React 中有许多性能优化的手段,useMemo 与 useCallback 是 hooks 推行后最为常用的两种方法,但是任何优化方案都是有成本的,如果组件都套上 useCallback、Memo,不仅代码可读性会变差,反而会增加性能消耗,还会因为参数的传递问题产生非预期的 bug。不是不推荐使用,必要时用可以达到更好的效果。
useCallback(同useEffect一样,根据[]是否变化来决定是否渲染)
// a, b 参数不变时,memoizedCallback 的引用不变
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
// a, b 参数不变时,memoizedValue 的值不变(即 computeExpensiveValue(复杂函数) 不被执行)
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
相同点
无论 useCallback 还是 useMemo 都是通过缓存来优化性能的,只不过 useCallback 缓存的是函数的引用,useMemo 缓存的则是(函数)返回值。两者可以做到等价:
useCallback(fn, deps) === useMemo(() => fn, deps))
//需要注意的是,JS 中函数被视为对象,除非他们引用相等,否则两个对象就算值一样,他们也不是全等的:
const foo1 = () => 'bar';
const foo2 = () => 'bar';
const foo1Ref = foo1;
console.log(foo1 === foo2) // false
console.log(foo1 === foo1Ref) // true
区别
useCallback 与 useMemo 返回的内容不同。
useCallback 会返回一个未执行的函数,而 useMemo 则返回执行完函数后的值。
const foo = () => 'bar';
const testCallback = useCallback(foo, []);
const testMemo = useMemo(foo, []);
console.log(testCallback); // foo() {}
console.log(testMemo); // bar
console.log(testCallback()); // bar
console.log(testMemo()); // TypeError: testMemo is not a function
何时使用
在使用函数式组件时,常常会产生组件的重渲染,而组件的重渲染又会带来函数的引用改变或值的重计算.
useMemo 适合用于大量数据运算的场景,如:
const value = React.useMemo(() => {
return Array(100000).fill('').map(v => v);
}, [a]);
useCallback 则适用于与 memo 搭配,减少由函数操作带来的重渲染,比如下面这个例子:
import React, { useState } from "react";
// 子组件
const Child = ({ handleChildClick }) => {
console.log("<=== Child render");
return <button onClick={handleChildClick}>handleChildClick</button>;
};
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const handleChildClick = () => {
console.log("Click Child");
};
const handleParentClick = () => {
console.log("Click Parent");
setCount(count => count + 1);
};
return (
<div>
Count: {count}
<hr />
<button onClick={handleParentClick}>handleParentClick</button>
<Child handleChildClick={handleChildClick} />
</div>
);
};
export default Parent;
在不加任何操作的情况下,每次点击 handleParentClick 的按钮,都会打印出 Child render 的信息:
<=== Child render // init
click Parent
<=== Child render
click Parent
<=== Child render
尝试给 handleChildClick 函数套上 useCallback:
const handleChildClick = useCallback(() => {
console.log("Click Child");
}, []);
//发现打印结果还是一样
再给 Child 套一层 memo:
const Child = memo(({ handleChildClick }) => {
console.log("<=== Child render");
return <button onClick={handleChildClick}>handleChildClick</button>;
});
此时符合预期:点击父组件不会打印"<=== Child render"
useMemo在什么场景下使用呢?
见参考文章
原理:
首先,将 Child 组件使用 memo 包裹,以达到 React.PureComponent 的效果,即能够对 props 进行浅比较,但是父组件因为 count 的改变,handleChildClick 会不断刷新引用,只有给这个函数套上 useCallback 进行缓存后,memo 的比较才能起作用,最终使得 Child 组件不被重复渲染,性能得到提升。
参考文章: