useMemo - React源码解析(四)
2022-06-06 本文已影响0人
小豆soybean
原文链接:https://www.jianshu.com/p/1df8300333f7
本文主要讲React Hook之一的useMemo。
useMemo是一个非常有用的Hook,其实现非常简单的。
Mount阶段
Mount阶段(也是初始化阶段,实例第一次执行到该钩子的过程),useMemo完成以下工作:
创建一个新的Hook实例;
计算传入useMemo回调函数的值;
将计算结果以及结果的依赖记录到Hook实例对象的memoizedState属性。
返回回调函数的值。
const HooksDispatcherOnMount: Dispatcher = {
// ...
useMemo: mountMemo,
// ...
};
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
// 创建一个新的Hook实例
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 计算传入`useMemo`回调函数的值
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Update阶段
在Update阶段与Mount阶段的处理逻辑是不同的。
获取Mount阶段创建的Hook实例
比较两次依赖的值是否相同
如果两次依赖的值相同,直接返回上一次的计算的结果
如果两次依赖的值不相同,则再次进行计算,并记录结果,返回新的结果。
const HooksDispatcherOnUpdate: Dispatcher = {
// ...
useMemo: updateMemo,
// ...
};
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
// 获取Mount阶段创建的Hook实例
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (prevState !== null) {
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 比较前后两次依赖是否相同
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 两次依赖的值相同,直接返回上一次的计算的结果
return prevState[0];
}
}
}
// 再次进行计算,并记录结果,返回新的结果
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Rerender阶段
Rerender阶段中的逻辑与Update阶段一样。这里略过不讲。
const HooksDispatcherOnRerender: Dispatcher = {
// ...
useMemo: updateMemo,
// ...
};
areHookInputsEqual函数
areHookInputsEqual函数的实现原理很简单,使用is函数,依次比较两个数组中的值。
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
if (__DEV__) {
if (ignorePreviousDependencies) {
// Only true when this component is being hot reloaded.
return false;
}
}
if (prevDeps === null) {
if (__DEV__) {
console.error(
'%s received a final argument during this render, but not during ' +
'the previous render. Even though the final argument is optional, ' +
'its type cannot change between renders.',
currentHookNameInDev,
);
}
return false;
}
if (__DEV__) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
if (nextDeps.length !== prevDeps.length) {
console.error(
'The final argument passed to %s changed size between renders. The ' +
'order and size of this array must remain constant.\n\n' +
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${prevDeps.join(', ')}]`,
`[${nextDeps.join(', ')}]`,
);
}
}
// 依次比较前后两个依赖中的值
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
React中对is的实现在shared包中:在支持Object.is的情况下直接调用Object.is函数; 不支持的情况下,是自实现的Object.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;
export default objectIs;