创见博客
实现useHandler自定义hook
七崽爱吃小饼干2026/03/02阅读 1

实现 useHandler 自定义 Hook

要实现一个引用永远不变的 useHandler Hook,让返回的函数始终能访问到最新的 props 和 state,这是 React 中非常经典的最新值捕获 + 稳定引用场景。

核心思路:

  1. 用 useRef 保存最新的回调函数(每次组件渲染都更新 ref)
  2. 用 useCallback 返回一个永久不变的包装函数
  3. 包装函数执行时,调用 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(回调函数, 依赖数组) 的工作规则:

  1. 组件第一次渲染
    • 生成一个函数,记录下此时的 props.a、state.b
    • 把这个函数缓存起来
  2. 后续重新渲染
    • 检查依赖数组有没有变化
    • 没变化 → 直接返回第一次缓存的旧函数
    • 旧函数里的变量,还是第一次渲染时的快照值

空依赖 [] = 永远用第一次的旧函数 + 旧变量 这就是拿不到最新值的根本原因!


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>;
};

三、核心原理讲解

  1. callbackRef

    • 只会在组件首次渲染时创建
    • 每次组件重新渲染,都会把最新的回调函数赋值给 callbackRef.current
    • 保证 ref 里永远存的是包含最新 props 和 state 的函数
  2. useCallback + 空依赖数组

    • 返回的包装函数引用永远不会改变(满足你「引用稳定不变」的需求)
    • 不会因为 props/state 变化而重新创建
  3. 执行逻辑

    • 点击按钮时,执行的是稳定的包装函数
    • 包装函数内部调用 ref.current(最新回调),自然就能拿到最新值

四、效果验证

  • 无论 props.a 怎么变、state.b 怎么变,handler 函数的引用永远不变
  • 每次点击按钮,打印的都是最新的 props.a 和 state.b
  • 完美解决 React 闭包陷阱 + 保持函数引用稳定

五、总结

  • 这个 useHandler 是 React 官方 useEvent Hook 的简易平替版
  • 核心:useRef 存最新值 + useCallback 保稳定引用
  • 无依赖、无副作用,可直接在项目中复用
评论
0/100