实现useHandler自定义hook
七崽爱吃小饼干2026/03/02阅读 1
实现 useHandler 自定义 Hook
要实现一个引用永远不变的 useHandler Hook,让返回的函数始终能访问到最新的 props 和 state,这是 React 中非常经典的最新值捕获 + 稳定引用场景。
核心思路:
- 用
useRef保存最新的回调函数(每次组件渲染都更新 ref) - 用
useCallback返回一个永久不变的包装函数 - 包装函数执行时,调用 ref 中存储的最新回调
一、为什么直接用 useCallback 拿不到最新的 props/state?
useCallback 会「缓存」函数和它依赖的变量,依赖不变,函数就永远不更新;
如果依赖漏写/空依赖,函数就会一直抱着旧变量不放,自然拿不到最新值。
根据React状态更新的不可变原则,每次更新state都是创建一个新的对象,不是直接在原来的引用上进行修改。而直接用useCallback的闭包,抓取的始终是最初的引用,所以不可能获得最新的值。
1. 先看你直接用 useCallback 的代码
jsx
const A = (props) => {
const [state,setState] = useState({b:1})
// 直接用 useCallback + 空依赖
const handler = useCallback((event) => {
console.log(props.a); // 永远是第一次渲染的旧值
console.log(state.b); // 永远是第一次渲染的旧值
}, []); // 空依赖 = 永远不更新函数
return <button onClick={handler}>click me</button>;
};
点击按钮,打印的永远是最开始的旧值。
2. 核心原理:useCallback 是「快照」机制
useCallback(回调函数, 依赖数组) 的工作规则:
- 组件第一次渲染
- 生成一个函数,记录下此时的
props.a、state.b - 把这个函数缓存起来
- 生成一个函数,记录下此时的
- 后续重新渲染
- 检查依赖数组有没有变化
- 没变化 → 直接返回第一次缓存的旧函数
- 旧函数里的变量,还是第一次渲染时的快照值
空依赖 [] = 永远用第一次的旧函数 + 旧变量
这就是拿不到最新值的根本原因!
3. 那我把依赖加上不就行了?
可以,但引用就不再稳定了:
jsx
const handler = useCallback(() => {
console.log(props.a, state.b);
}, [props.a, state.b]); // 加上依赖
✅ 能拿到最新值
❌ 但 props.a / state.b 一变,handler 引用就变
这就违背了你最开始的需求:引用稳定不变。
二、完整实现代码
jsx
import { useRef, useCallback } from 'react';
const useHandler = (cb) => {
// 1. 创建 ref 存储最新的回调函数
const callbackRef = useRef(cb);
// 2. 每次渲染都更新 ref,保证存储的是最新的回调(包含最新props/state)
callbackRef.current = cb;
// 3. 返回一个永远不变的包装函数(依赖数组为空,引用永久稳定)
return useCallback((...args) => {
// 执行时调用最新的回调
return callbackRef.current(...args);
}, []);
};
// 你的原组件(直接使用即可)
const A = (props) => {
const [state,setState] = useState({b:1})
const handler = useHandler((event) => {
console.log(props.a); // 永远是最新值
console.log(state.b); // 永远是最新值
});
return <button onClick={handler}>click me</button>;
};
三、核心原理讲解
-
callbackRef- 只会在组件首次渲染时创建
- 每次组件重新渲染,都会把最新的回调函数赋值给
callbackRef.current - 保证 ref 里永远存的是包含最新
props和state的函数
-
useCallback+ 空依赖数组- 返回的包装函数引用永远不会改变(满足你「引用稳定不变」的需求)
- 不会因为 props/state 变化而重新创建
-
执行逻辑
- 点击按钮时,执行的是稳定的包装函数
- 包装函数内部调用
ref.current(最新回调),自然就能拿到最新值
四、效果验证
- 无论
props.a怎么变、state.b怎么变,handler函数的引用永远不变 - 每次点击按钮,打印的都是最新的
props.a和state.b - 完美解决 React 闭包陷阱 + 保持函数引用稳定
五、总结
- 这个
useHandler是 React 官方useEventHook 的简易平替版 - 核心:
useRef存最新值 +useCallback保稳定引用 - 无依赖、无副作用,可直接在项目中复用