You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中自定义useWatch1与useWatch2表现不符的原因探究

问题描述

我实现了两个自定义Hook useWatch1 和 useWatch2,它们的作用是当监听值变化时执行指定的回调函数cb。

操作流程:

  • 多次点击ADD COUNT按钮使count值增加
  • 再点击ADD ID按钮,此时预期count值被设为0

实际结果:useWatch2符合预期,但useWatch1无法实现该效果,请问原因是什么?

代码示例

import { useRef, useState } from "react";

function useWatch1(current, cb) {
  const isFirstRender = useRef(true);
  const prevRef = useRef(null);

  if (isFirstRender.current) {
    prevRef.current = current;
    isFirstRender.current = false;
  } else if(prevRef.current !== current){
    prevRef.current = current;
    cb();
  }
}

function useWatch2(current, cb) {
  const [prev, setPrev] = useState(current);

  if (current !== prev) {
    setPrev(current);
    cb();
  }
}

function MyCpm({ id }) {
  const [count, setCount] = useState(0);

  // useWatch1(id, () => {
  //   setCount(0);
  // });

  useWatch2(id, () => {
    setCount(0);
  });

  console.log("render", count);

  return (
    <>
      <button
        onClick={() => {
          setCount((c) => c + 1);
        }}
      >
        ADD COUNT
      </button>
      <span>count: {count}</span>
      <span>-----</span>
      <span>id: {id}</span>
    </>
  );
}

function App() {
  const [id, setId] = useState(0);

  return (
    <>
      <button
        onClick={() => {
          setId((id) => id + 1);
        }}
      >
        ADD ID
      </button>
      <MyCpm id={id}></MyCpm>
    </>
  );
}

export default App;

原因分析

useWatch1失效的核心问题

useWatch1直接在组件渲染阶段执行了状态更新回调cb()(也就是setCount(0)),这违反了React的渲染规则:渲染过程中不能触发状态更新。

当点击ADD ID导致MyCpm因id变化重新渲染时,useWatch1检测到id变化后立刻调用cb(),触发setCount(0)。这会让组件马上进入新一轮渲染,但此时prevRef.current已经被更新为新的id,新一轮渲染中useWatch1不会再执行回调,而这次状态更新会被React的批处理机制干扰,最终导致count没有被正确重置。

另外,useWatch1把副作用逻辑写在渲染阶段的if分支里,完全不符合React的Hook设计原则——副作用必须放在useEffect这类专门的副作用Hook中执行,不能直接嵌入渲染流程。

useWatch2能生效的原因

useWatch2用useState保存之前的值,当current !== prev时先调用setPrev(current)触发组件重渲染,同时执行cb()。虽然这种写法也有瑕疵(可能触发两次渲染),但setPrev和setCount会被React合并批处理,最终cb()里的setCount(0)能被正确执行,实现count重置的效果。

正确的useWatch实现方式

要实现稳定的监听值变化执行回调,应该用useEffect处理副作用,这是React官方推荐的方式:

function useWatch(current, cb) {
  const prevRef = useRef(current);
  
  useEffect(() => {
    if (prevRef.current !== current) {
      cb();
      prevRef.current = current;
    }
  }, [current, cb]);
}

这个版本把监听逻辑放在useEffect中,确保是在组件渲染完成后执行副作用,不会干扰渲染流程,能稳定监听值的变化并执行回调。

内容的提问来源于stack exchange,提问作者user15163984

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:34:51