概述
在React中,useCallback和useMemo分别用于缓存函数和变量,当其依赖没发生改变时,可以复用旧的函数或变量,一定程度上可以优化函数组件的性能。
源码分析
useCallback
- 挂载(首次渲染)
函数组件在挂载首次渲染时,useCallback调用的方法是mountCallback。
js
// callback 表示回调函数,deps是依赖数组,下同
function mountCallback(callback, deps) {
// 调用 mountWorkInProgress方法创建新的hook,并将其放到fiber的memoizedState链表上,最后返回新的hook
const hook = mountWorkInProgressHook();
// 判断依赖,若不存在,则为null
const nextDeps = deps === undefined ? null : deps;
// 将callback和deps 存放到fiber.memoizedState中,以便后续复用
hook.memoizedState = [callback, nextDeps];
// 最后返回callback
return callback;
}
- 组件更新时
函数组件在更新时,hook 的分发器dispatch 会将useCallback指向updateCallback。
其源码实现如下:
js
function updateCallback(callback, deps) {
// 调用updateWorkInProgressHook读取旧Fiber的对应旧hook,以及复用旧hook,创建新hook并返回
const hook = updateWorkInProgressHook();
// 新的依赖取值
const nextDeps = deps === undefined ? null : deps;
// 从新hook上取hook之前数据
const prevState = hook.memoizedState;
// 若新依赖不等于null
if (nextDeps !== null) {
const prevDeps = prevState[1];
// 则判断新旧依赖是否发生了改变
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 若没变化,则直接返回之前的callback
return prevState[0];
}
}
// 若新依赖不存在,或者新旧依赖不等,则再次将callback和deps依赖存入到fiber中
hook.memoizedState = [callback, nextDeps];
// 返回callback
return callback;
}
useMemo
useMemo缓存的是回调函数的返回值,useCallback缓存的是回调函数,因此useMemo不过就是比useCallback的回调函数执行一次,并将其结果存到fiber.memoizedState的链表中,并返回;后续更新调度时,若依赖没发生改变,就读取旧hook中的值;若发生改变,再次执行回调函数,将它的返回值存起来,同时返回即可。
其源码实现如下:
js
function mountMemo(
nextCreate,
deps,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function updateMemo(
nextCreate,
deps,
) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
总结
useCallback和useMemo就是将函数和依赖存放在fiber.meomizedState的链表中。在函数组件重新渲染后,若依赖没有变化,就复用之前的旧值。若useRef这个hook 的值不与DOM绑定,只是在函数组件内部使用,在函数组件更新后,也是复用旧值。它们的原理类似。