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

React严格模式下useState结合useRef时状态重置问题

问题:React严格模式下useState状态莫名重置为默认值

在Next.js开发模式(默认开启React严格模式)中,发现useState状态会自行重置为默认值,该问题仅在严格模式下复现。

复现代码

const { useEffect, useState, useRef } = React;

function useFakeQuery() {
  const [hookState, setHookState] = useState(null);
  useEffect(() => {
    console.log("starting timeout");
    const timeout = setTimeout(() => {
      console.log("timeout completed, returning response");
      setHookState("response");
    }, 1000);
    return function cleanup() {
      console.log("cancelling timeout, no response");
      clearTimeout(timeout);
    }
  }, [setHookState]);
  return hookState;
}

const Example = () => {
  useEffect(() => {
    console.log("mount");
    return function cleanup() {
      console.log("unmount");
    }
  }, []);

  const [myState, setMyState] = useState("default");
  const queryResponse = useFakeQuery();

  console.log("state is", myState, ", query response is", queryResponse);

  const loadedRef = useRef(false);
  if (!loadedRef.current && queryResponse != null) {
    console.log("query has responded (for the first time) with", queryResponse);
    loadedRef.current = true;
    if (myState === "default") {
      console.log("state was still at default, so setting state to query response:", queryResponse);
      setMyState(queryResponse);
    }
  }

  return <p>State: {myState}</p>;
}


const App = () => {
  return(
    <React.StrictMode>
      <Example />
    </React.StrictMode>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

现象说明

严格模式下组件会先挂载再卸载,随后重新挂载,这是预期行为。第一次挂载的定时器被取消,第二次挂载的定时器完成后,Example组件将loadedRef设为true,并通过setMyState把myState设为"response"。

之后有一次渲染日志显示state is response , query response is response,符合预期;但紧接着又一次渲染日志显示state is default , query response is response,状态莫名重置为默认值,渲染输出变为"State: default"。

补充信息:

  • 若用状态而非ref记录hook是否返回响应,问题会消失,但认为ref是合理实现方式,希望确认是否理解有误。
  • 关闭严格模式问题消失,但不想关闭严格模式。

原因分析

这不是React的Bug,而是你在渲染阶段直接修改ref并调用setState的写法违反了React规则,加上严格模式的特殊行为放大了问题:

  1. 严格模式的双重执行机制:React 18严格模式在开发环境下,会对组件渲染逻辑、useEffect等进行两次执行(包括组件函数本身、useState初始化、ref赋值等),目的是检测不安全的副作用。
  2. 渲染阶段的不可变性要求:React规定,渲染阶段(组件函数执行过程)不能有副作用,包括直接修改ref、调用setState。你的代码在组件函数执行时直接修改loadedRef.current = true并调用setMyState,属于渲染阶段的非法操作。
  3. ref状态的不一致:严格模式下,React会对非法的渲染阶段副作用进行检测,可能会“撤销”这类修改,导致ref状态在后续渲染中出现不一致,最终让setState的操作被抵消,状态回到默认值。

正确写法

把依赖queryResponse的逻辑移到useEffect中——useEffect是React专门处理副作用的钩子,不会受严格模式双重执行的影响:

const Example = () => {
  useEffect(() => {
    console.log("mount");
    return function cleanup() {
      console.log("unmount");
    }
  }, []);

  const [myState, setMyState] = useState("default");
  const queryResponse = useFakeQuery();
  const loadedRef = useRef(false);

  console.log("state is", myState, ", query response is", queryResponse);

  // 将逻辑移至useEffect中处理
  useEffect(() => {
    if (!loadedRef.current && queryResponse != null) {
      console.log("query has responded (for the first time) with", queryResponse);
      loadedRef.current = true;
      if (myState === "default") {
        console.log("state was still at default, so setting state to query response:", queryResponse);
        setMyState(queryResponse);
      }
    }
  }, [queryResponse, myState]);

  return <p>State: {myState}</p>;
}

为什么用状态代替ref问题会消失?

因为状态更新由React管理,符合React数据流规则,不会在渲染阶段产生非法副作用,严格模式的双重执行不会影响状态的正确流转。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 15:30:52