React 18升级后Hook无法立即更新数据问题求助
React 18 升级后自定义 Hook 数据更新渲染异常的解决方法
问题根源拆解
- React 18 Strict Mode 特性:开发环境下,React 18 的严格模式会自动重复执行
useEffect(包括订阅/取消订阅流程),用来检测潜在的内存泄漏或非幂等副作用问题,这是导致你看到两次set new data日志的直接原因。 - 依赖数组错误:你的
useEffect依赖里写了planId,但实际传入的参数是id,这会导致依赖不匹配,触发不必要的重复渲染和订阅。 - 闭包与依赖循环:
useEffect依赖数组包含了data,每次data更新都会重新创建handleDataChange并重新订阅,加上初始的handleDataChange()调用,形成了循环更新的隐患,同时回调里引用的data可能是闭包中的旧值,导致判断逻辑失效。 - 初始数据获取方式:直接用
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>; };
关键修改点说明
- 优化初始数据获取:通过
useState(() => ...)的初始化函数形式,避免每次渲染都执行数据查询,仅在首次挂载时执行一次。 - 用 Ref 解决闭包问题:
idRef和dataRef保存最新的参数和状态值,确保订阅回调能拿到实时数据,不会因闭包捕获旧值导致判断错误。 - 精简依赖数组:只将
id作为useEffect的依赖,确保只有当id变化时才重新订阅,避免因data变化触发的循环更新。 - 函数式状态更新:使用
setData(prev => ...)确保状态更新基于最新的前值,同时同步更新dataRef,保持数据一致性。
额外说明
关于开发环境下的两次 useEffect 执行:这是 React 18 Strict Mode 的正常特性,生产环境不会出现,目的是帮助你提前发现副作用中的问题(比如未正确清理的订阅)。只要你的 unsubscribe 方法能正确取消订阅,就不会有实际影响。
内容的提问来源于stack exchange,提问作者Tommy
相关产品推荐
相关产品推荐

