之前的文章记录了useId,useRef,useState钩子函数的使用和原理,其中useState的执行会触发组件的重新渲染,而useId,useRef不会引发组件的重新渲染。
接下来的要记录的钩子函数useCallback,useMemo是缓冲相关的钩子函数更新不会触发组件重新渲染。
useCallback、useMemo的基本使用
import React, { useMemo, useState } from 'react';
const ExpensiveCalculation = () => {
const [count, setCount] = useState(0);
const expensiveValue = useMemo(() => {
// 模拟一个高开销的计算
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum + count;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default ExpensiveCalculation;
import React, { useCallback, useState } from 'react';
const CallbackExample = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
};
export default CallbackExample;
useCallback
function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}
mountCallback
React 中 useCallback
Hook 的 挂载阶段 实现,主要用于 缓存回调函数 以保持引用稳定性。
函数参数含义:
- callback:想要缓存的函数。此函数可以接受任何参数并且返回任何值。在初次渲染时,React 将把函数返回给你(而不是调用它!)。当进行下一次渲染时,如果
deps
相比于上一次渲染时没有改变,那么 React 将会返回相同的函数。否则,React 将返回在最新一次渲染中传入的函数,并且将其缓存以便之后使用。React 不会调用此函数,而是返回此函数。你可以自己决定何时调用以及是否调用。deps
:有关是否更新fn
的所有响应式值的一个列表。响应式值包括 props、state,和所有在你组件内部直接声明的变量和函数。如果你的代码检查工具 配置了 React,那么它将校验每一个正确指定为依赖的响应式值。依赖列表必须具有确切数量的项,并且必须像[dep1, dep2, dep3]
这样编写。React 使用 Object.is 比较每一个依赖和它的之前的值。
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
// 创建新的 Hook 对象并添加到当前 Fiber 节点的 Hook 链表中。
const hook = mountWorkInProgressHook();
// 处理依赖数组
const nextDeps = deps === undefined ? null : deps;
// 缓存回调函数
hook.memoizedState = [callback, nextDeps];
return callback;
}
updateCallback
React 中 useCallback
Hook 的 更新阶段 实现,主要用于 比较依赖项 并决定是否返回缓存的回调函数。
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
// 获取当前 Hook
const hook = updateWorkInProgressHook();
// 处理依赖数组
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// 比较依赖项
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
// 更新缓存并返回新函数
hook.memoizedState = [callback, nextDeps];
return callback;
}
useMemo
function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
mountMemo
React 中 useMemo
Hook 的 挂载阶段 实现,主要用于 缓存计算结果 以避免重复计算。
函数参数含义:
- nextCreate:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。React 将会在首次渲染时调用该函数;在之后的渲染中,如果 deps 没有发生变化,React 将直接返回相同值。否则,将会再次调用 nextCreate 并返回最新结果,然后缓存该结果以便下次重复使用。
- deps:所有在 nextCreate 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成
[dep1, dep2, dep3]
这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
// 未提供依赖数组,设置为null
const nextDeps = deps === undefined ? null : deps;
// 计算结果
const nextValue = nextCreate();
// 缓存结果
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
updateMemo
React 中 useMemo
Hook 的 更新阶段 实现,主要用于 比较依赖项 并决定是否重新计算缓存值。
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
// 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;
}
工具函数之 areHookInputsEqual
React 中用于 比较 Hook 依赖项 的核心函数,主要用于 useMemo
、useCallback
和 useEffect
中判断依赖项是否变化。
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
// 组件首次挂载时,prevDeps 为 null。
if (prevDeps === null) {
// 直接返回 false,表示依赖项变化,触发计算 / 执行。
return false;
}
// 逐项浅比较
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
// 使用 is 函数(类似 Object.is)比较每个元素。
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
// 若任意元素不相等,立即返回 false。
return false;
}
return true;
}
工具函数之 objectIs
该函数主要用于判断两个值是否严格相等,处理了 JavaScript 中一些特殊的相等性比较场景。
is
函数的主要作用是:
- 基础严格相等:使用
===
判断基本相等性。 - 特殊值处理:
-
- 区分
+0
和-0
(返回false
)。 - 处理
NaN
(NaN
与NaN
返回true
)。
- 区分
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;