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

React 18升级后Hook无法立即更新数据问题求助

React 18 升级后自定义 Hook 数据更新渲染异常的解决方法

问题根源拆解

  1. React 18 Strict Mode 特性:开发环境下,React 18 的严格模式会自动重复执行 useEffect(包括订阅/取消订阅流程),用来检测潜在的内存泄漏或非幂等副作用问题,这是导致你看到两次 set new data 日志的直接原因。
  2. 依赖数组错误:你的 useEffect 依赖里写了 planId,但实际传入的参数是 id,这会导致依赖不匹配,触发不必要的重复渲染和订阅。
  3. 闭包与依赖循环:useEffect 依赖数组包含了 data,每次 data 更新都会重新创建 handleDataChange 并重新订阅,加上初始的 handleDataChange() 调用,形成了循环更新的隐患,同时回调里引用的 data 可能是闭包中的旧值,导致判断逻辑失效。
  4. 初始数据获取方式:直接用 DataManager.selectCurrentDataById(id) 作为 useState 的初始值,会在每次组件渲染时都执行一次这个方法,可能导致数据不一致。

解决方案及修改后的代码

import * as _ from "lodash";
import * as React from "react";

const useData = (id: string): string[] => {
    // 用初始化函数获取初始数据,仅在组件首次挂载时执行一次
    const [data, setData] = React.useState(() => DataManager.selectCurrentDataById(id));
    // 用 ref 保存最新的 id 和 data,解决闭包中值过时的问题
    const idRef = React.useRef(id);
    const dataRef = React.useRef(data);

    React.useEffect(() => {
        // 更新 ref 中的最新值
        idRef.current = id;
        dataRef.current = data;

        const handleDataChange = () => {
            const currentId = idRef.current;
            const newData = DataManager.selectCurrentDataById(currentId);
            const currentData = dataRef.current;

            if (_.isEqual(newData, currentData)) return;
            console.log("set new data ", newData);
            // 函数式更新确保拿到最新状态,同时更新 ref
            setData(prev => {
                dataRef.current = newData;
                return newData;
            });
        };

        // 执行订阅并保存取消订阅方法
        const unsubscribe = DataManager.subscribe(handleDataChange);
        // 初始调用确保数据是最新的
        handleDataChange();

        return unsubscribe;
    }, [id]); // 仅依赖 id,避免因 data 变化触发重复订阅

    console.log("return data ", data);
    return data;
};

const MatcherComponent = ({ id }: { id: string }) => {
    const data = useData(id);
    console.log("data ", data);
    
    return <TestType type={data[0]} />;
};

const TestType = ({ type }: { type: string }) => {
    console.log("type ", type);
    return <div>{type}</div>;
};

关键修改点说明

  1. 优化初始数据获取:通过 useState(() => ...) 的初始化函数形式,避免每次渲染都执行数据查询,仅在首次挂载时执行一次。
  2. 用 Ref 解决闭包问题:idRef 和 dataRef 保存最新的参数和状态值,确保订阅回调能拿到实时数据,不会因闭包捕获旧值导致判断错误。
  3. 精简依赖数组:只将 id 作为 useEffect 的依赖,确保只有当 id 变化时才重新订阅,避免因 data 变化触发的循环更新。
  4. 函数式状态更新:使用 setData(prev => ...) 确保状态更新基于最新的前值,同时同步更新 dataRef,保持数据一致性。

额外说明

关于开发环境下的两次 useEffect 执行:这是 React 18 Strict Mode 的正常特性,生产环境不会出现,目的是帮助你提前发现副作用中的问题(比如未正确清理的订阅)。只要你的 unsubscribe 方法能正确取消订阅,就不会有实际影响。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:22:14