hello 大家好
如果看过我之前的 useMemo源码解析 这篇文章后 那么你接下来看useCallback源码解析 就会简单很多 废话不多说 上才艺!
useCallback 是 React 提供的一个 Hook,用于返回一个 memoized(记忆化)版本的回调函数。其主要目的是优化性能,避免不必要的渲染或重新计算。
解释
当组件重新渲染时,函数组件内部的所有东西都会重新运行。这意味着每次渲染都会创建一个新的回调函数。在大多数情况下,这没什么问题,因为函数创建的成本相对较低。但在某些场景中,重新创建函数可能导致性能问题或不必要的行为,如:
- 如果一个回调函数作为 prop 传递给了一个子组件,子组件可能会因为接收到新的回调函数 prop 而进行不必要的重新渲染。
- 如果回调函数用于事件处理并且频繁地被重新创建,可能会对性能产生微小的影响。
在这些情况下,useCallback 可以帮助你确保回调函数的引用在多次渲染之间保持不变,除非它依赖的值发生了变化。
用法
useCallback 接受两个参数:
- 回调函数
- 依赖项数组
返回值是 memoized 的回调函数。
示例
假设你有一个组件,它接受一个 onPress 回调,并且你想确保这个回调只在某个特定的依赖项改变时才被重新创建:
import React, { useState, useCallback } from 'react';
function MyComponent(props) {
const [count, setCount] = useState(0);
const handlePress = useCallback(() => {
console.log("Button was pressed!");
setCount(count + 1);
}, [count]); // 依赖于 `count` 变量
return (
<div>
<button onClick={handlePress}>Press me</button>
<p>Button pressed {count} times.</p>
</div>
);
}
在上述示例中,handlePress 回调函数只有在 count 发生变化时才会被重新创建。如果有其他状态或 props 发生变化并导致 MyComponent 重新渲染,handlePress 的引用仍然保持不变。
注意
- 不要在
useCallback的依赖数组中遗漏任何值。如果回调使用了组件的某个 state 或 prop,确保将它包含在依赖数组中。 - 在许多情况下,你可能并不需要
useCallback。只有当你确实遇到性能问题或不必要的渲染时,才考虑使用它。过度优化可能会使代码更难理解并可能引入错误。
以上案例将useCallback基本功能讲解完毕,下面我们进行useCallback源码分析
源码在packages/react-reconciler/src/ReactFiberHooks.js 中可以找到
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
// 获取hook状态函数
const hook = updateWorkInProgressHook();
// 确定下一个依赖项数组
const nextDeps = deps === undefined ? null : deps;
// 获取前一个状态的回调和依赖
// prevState是一个数组,其中prevState[0]是之前的回调,而prevState[1]是之前的依赖数组
const prevState = hook.memoizedState;
if (nextDeps !== null) {
// 获取依赖数组
const prevDeps: Array<mixed> | null = prevState[1];
//比较依赖 类似于Object.is();
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 如果新的和旧的依赖项数组相同,则直接返回先前的回调函数,因为没有必要更新它
return prevState[0];
}
}
//如果新的依赖项和旧的不相等或 nextDeps 为 null,则将新的回调函数和依赖项数组保存到 hook 的状态中。
hook.memoizedState = [callback, nextDeps];
return callback;
}
总结
- 该函数首先通过调用
updateWorkInProgressHook()函数来获取当前的 hook 状态。 - 确定下一个依赖项数组(
nextDeps);如果deps为undefined,则设其为null。 - 从
hook的memoizedState中获取先前状态的回调和依赖项。这个状态是一个数组,其中第一个元素是之前的回调,第二个元素是之前的依赖数组。 - 如果
nextDeps不为null,则:- 从先前状态中获取旧的依赖数组。
- 使用
areHookInputsEqual函数比较新旧依赖项。这个函数的功能似乎类似于Object.is(),用于深度比较。 - 如果新的依赖项数组与旧的相同,那么就直接返回先前的回调函数,因为没有必要更新它。
- 如果新的依赖项数组与旧的不相同,或者
nextDeps为null,则将新的回调函数和依赖项数组保存到hook的状态 (memoizedState) 中。 - 最后返回新的回调函数。