一、useMemo
1、语法与参数
js
const cacheValue = useMemo(calculateValue, dependencies);
(1)calculateValue参数
calculateValue表示要计算缓存值的函数;
- 该函数是一个没有任何参数的纯函数,可以返回任意类型;
- React会在首次渲染时调用该函数;在更新时,会根据dependencies是否变化决定是否需要调用该函数;
- 如果dependencies没有发生变化,React将直接返回相同值;
- 如果dependencies发生变化,React会调用该函数计算最新结果并返回;
(2)dependencies参数
用于判断calculateValue是否更新的依赖项;
依赖项列表中的值包括:props、state、所有在组件内部直接声明的变量和函数;
2、作用
在每次重新渲染时缓存计算的结果,跳过组件的重新渲染;【默认情况下,当一个组件重新渲染时,React会递归地重新渲染它的所有子组件】
该hook可以作为性能优化的手段,可以将每次渲染都需要大量计算的内容缓存起来;
传入useMemo的函数在渲染期间执行;
useMemo不会让首次渲染更快,只是会帮助你跳过不必要的更新;
3、返回值
初次渲染:返回calculateValue函数调用返回的结果;
更新渲染:依赖没有变化,返回上一次渲染中缓存的结果;
依赖变化,返回这一次渲染传入的calculateValue函数调用返回的结果;
4、useMemo与useCallback的区别
- useCallback缓存的是传入的fn函数,不会调用该函数;
- useMemo缓存的是函数的调用结果,在调用useMemo会直接调用传入的函数;
二、useMemo源码
1、mountMemo - 首次渲染
js
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate(); // 直接调用传入的回调函数,并获取其返回值
hook.memoizedState = [nextValue, nextDeps]; // 缓存的内容
return nextValue; // 返回我们需要的结果
}
2、updateMemo - 更新渲染
js
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps; // 获取下一次更新时的新依赖
const prevState = hook.memoizedState; // 获取的其实是我们之前缓存的内容:[nextValue, nextDeps] 缓存的计算结果及其依赖
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1]; // 获取旧的依赖信息
if (areHookInputsEqual(nextDeps, prevDesps)) {
return prevState[0]; // 若依赖没有发生变化,则直接返回我们缓存的结果,不会调用传入的回调函数
}
}
}
// 不满足上述条件,则直接调用回调函数,重新计算结果
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps]; // 重新缓存结果及依赖
return nextValue;
}
【小结】
从源码分析,我们可以得出结论:在首次渲染时,传入的回调函数一定会执行一次;而在更新阶段,我们会对比新旧依赖,判断是否需要调用函数重新计算结果,若依赖没有发生变化,则直接返回我们缓存的结果,无需再次调用函数计算一次,从而可以跳过一些代价昂贵的重新计算。
三、useCallback
1、定义
useCallback是一个允许你在多次渲染中缓存函数的React Hook;在组件顶层调用;
2、语法及参数
js
const cacheFn = useCallback(fn, dependencies);
(1)fn参数
fn表示想要缓存的函数;该函数可以接受任何参数并且返回任何值;
React不会调用该函数,而是在首次渲染时返回该函数;
在下一次渲染时,若dependencies没有发生变化,React将会返回相同的函数;
(2)dependencies参数
用于判断fn是否更新的依赖项;
依赖项列表中的值包括:props、state、所有在组件内部直接声明的变量和函数;
3、作用
允许你在多次渲染中缓存函数;
useCallback
不会阻止创建函数;每次组件重新渲染时,还是会创建一个新的函数,但是如果依赖没有变化,那么React会忽略它并返回缓存的函数;
4、返回值
- 初次渲染:返回传入的函数fn;
- 更新渲染:依赖没有变化,返回上一次渲染中缓存的fn函数;
依赖变化,返回这一次渲染传入的fn函数;
5、useCallback的使用场景
- 将其作为props传递给包装在memo中的组件;
- 如果props没有更改,则希望跳过重新渲染;
- 缓存允许组件仅在依赖项更改时重新渲染;
- 传递的函数可能作为某些hook的依赖;
- 建议将自定义hook返回的任何函数包裹在useCallback中;
【使用useMemo实现useCallback】
js
function useCallback(fn, deps) {
return useMemo(() => fn, deps); // 使用useMemo返回传入的fn
}
四、useCallback源码
1、mountCallback - 首次渲染
js
function mountCallback<T>(
callback: T,
deps: Array<mixed> | void | null
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps]; // 直接缓存传入的callback,没有调用
return callback;
}
2、updateCallback - 更新渲染
js
function updateCallback<T>(
callback: T,
deps: Array<mixed> | void | null
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInoutsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}
【代码补充】判断新旧依赖是否相同
js
function areHookInputsEqual(
nextDeps: Array<mixed>, // 新的依赖
prevDeps: Array<mixed> | null, // 旧的依赖
) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
// 不相同则返回false
return false;
}
return true;
}
// is函数
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
const objectIs: (x: any, y: any) => boolean =
typeof Object.is === 'function' ? Object.is : is;