useMemo和useCallback都起到缓存的作用,seMemo缓存函数结果,useCallback是缓存函数。 两者的参数都是第一个为需要缓存的函数,第一个为依赖项数组(判断是否需要重新执行)
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
把函数传入第一个参数,仅仅在a,b改变时,才会重新触发函数,假如函数中有大量的计算,接可以节省很大开销。
不使用useMemo
function Example() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
function getNum() {
return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
}
return <div>
<h4>总和:{getNum()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
可以看到每次当val改变时getNum都会重新进行计算,但是我们计算没用到Val,并不需要,造成了无效的性能开销。
使用useMemo
function Example() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const getNum = useMemo(() => {
return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
}, [count])
return <div>
<h4>总和:{getNum()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
使用useMemo只有当count发生变化时,才会重新计算。
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo和useCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。
例如我们传给子组件一个函数,每次重新渲染的时候都会生成一个新函数,导致子组件重新渲染,useCallback
function Parent() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const getNum = useCallback(() => {
return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
}, [count])
return <div>
<Child getNum={getNum} />
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
const Child = React.memo(function ({ getNum }: any) {
return <h4>总和:{getNum()}</h4>
})
使用useCallback之后,仅当count发生变化时Child组件才会重新渲染,而val变化时,Child组件是不会重新渲染的。
源码分析
useMemo和useCallback源码非常相似,首次挂载走mount,组件更新时走的是update。
// mount阶段就是获取到传入的回调函数和依赖数组,保存到hook的memorizedState中,然后返回回调函数。
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
// update阶段
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
// 从hook的memorizedState中获取上次保存的值[callback, deps],
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 比较新的deps和之前的deps是否相等
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 如果相等,返回memorized的callback
return prevState[0];
}
}
}
// 如果deps发生变化,更新hook的memorizedState,并返回最新的callback
hook.memoizedState = [callback, nextDeps];
return callback;
}
这立就只拿callback源码分析了,大致思想都一致 思想就是mount阶段获取传入的回调函数和依赖数组,保存到hook.memorizedState中,更新阶段将传入的依赖项跟上一次的相对比,决定是创建新的还是返回之前的值。