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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 12:55:19