useEffect执行后多组件状态引用相同的问题分析与解决
问题:多个组件共享状态异常,尽管已复制对象
我尝试给多个MyComponent组件传入不同的初始化状态,但最终所有组件渲染出相同的内容。虽然useEffect里的日志输出是正确的,但渲染结果不符合预期。我知道这和JavaScript的引用机制有关,但即使复制了对象副本,问题还是存在。目前用JSON.parse(JSON.stringify(newContent))能规避,但想知道问题本质和更简洁的解决办法。
相关代码
App组件
export default function App() { return ( <div className="App"> <h1>My component</h1> <MyComponent id="component-1" initContent="component-1-content" /> <MyComponent id="component-2" initContent="component-2-content" /> </div> ); }
MyComponent组件
import { useEffect, useState } from "react"; type OBJECT_TYPE = { text: string; children: { text: string; }[]; }; const EMPTY_OBJECT: OBJECT_TYPE = { text: "", children: [ { text: "" } ] }; export const MyComponent = ({ id, initContent }: { id: string; initContent: string; }) => { const [contentObject, setContent] = useState<[OBJECT_TYPE] | undefined>(); useEffect(() => { const newContent = { ...EMPTY_OBJECT }; // copy the object newContent.children[0].text = initContent; console.log(newContent.children[0].text); // logs out correctly setContent([newContent]); }, [initContent]); if (!contentObject) return null; return <div>{contentObject[0].children[0].text}</div>; // Wrong render };
问题本质
你使用{ ...EMPTY_OBJECT }执行的是浅拷贝操作:
- 浅拷贝只会复制对象的第一层属性值,对于
EMPTY_OBJECT里的children数组(引用类型),拷贝后的newContent.children和原EMPTY_OBJECT.children指向同一个内存地址的数组。 - 当第一个组件修改
newContent.children[0].text时,实际是直接修改了共享的EMPTY_OBJECT.children数组内的对象。第二个组件初始化时,浅拷贝得到的children依然是这个已经被修改过的数组引用,最终所有组件的children[0].text都会被最后一次修改的值覆盖。 - 日志输出正确是因为修改后立刻打印的是当前组件的
initContent,但后续其他组件修改共享引用后,所有组件的状态会同步变化,因为它们的contentObject里的children指向同一个数组。
简洁解决方案
方案1:手动深拷贝嵌套引用类型
手动复制children数组及其内部对象,彻底切断引用共享:
useEffect(() => { const newContent = { ...EMPTY_OBJECT, children: [ { ...EMPTY_OBJECT.children[0] } // 复制数组内的对象 ] }; newContent.children[0].text = initContent; setContent([newContent]); }, [initContent]);
方案2:直接创建全新对象
完全跳过EMPTY_OBJECT的引用,直接构建独立对象:
useEffect(() => { const newContent: OBJECT_TYPE = { text: "", children: [{ text: initContent }] }; setContent([newContent]); }, [initContent]);
方案3:函数式初始化状态(最优解)
不需要依赖useEffect,直接在useState中用函数初始化状态,组件挂载时就生成独立对象,从根源避免引用问题:
export const MyComponent = ({ id, initContent }: { id: string; initContent: string; }) => { // 函数式初始化,每次组件实例化都会生成全新对象 const [contentObject, setContent] = useState<[OBJECT_TYPE]>(() => [ { text: "", children: [{ text: initContent }] } ]); return <div>{contentObject[0].children[0].text}</div>; };
内容的提问来源于stack exchange,提问作者Eskild Mageli
相关产品推荐
相关产品推荐

