reactjs 为什么即使我创建了单独的组件,useState的状态也是共享的?

lmyy7pcs  于 2023-06-22  发布在  React
关注(0)|答案(5)|浏览(98)

我目前正在使用React组件,我在Area component中遇到了共享状态的问题。组件有一个count state,当创建组件的多个示例时就会出现问题。

function Area({ title }) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>current area : {title}</h2>
      <div>
        <button type="button" onClick={() => setCount(count + 1)}>
          +1
        </button>
        <div>count : {count}</div>
      </div>
    </div>
  );
}

const menuInfos = [
  { title: "aaaaaa", children: <Area title="aaaaaa" /> },
  { title: "bbbbbb", children: <Area title="bbbbbb" /> },
  { title: "cccccc", children: <Area title="cccccc" /> },
  { title: "dddddd", children: <Area title="dddddd" /> },
  { title: "eeeeee", children: <Area title="eeeeee" /> }
];

在当前代码中,Area componentMenu component中使用,并为每个菜单项创建一个示例。但是,当Area component跨多个菜单项使用时,count变量是共享的,这会导致意外的行为。

代码沙箱工作链接:https://codesandbox.io/s/goofy-wind-59v5j9?file=/src/_app.js
我想了解这项工作的原因,并找到一个解决方案,以管理计数状态的每一个菜单项。我将感谢任何关于如何修改代码以实现这一目标的建议。
我已经利用Area component中的useEffect hook使用console.log记录卸载阶段,卸载过程似乎运行正常。 此外,我使用www.example.com比较了menuInfos array的子组件(作为props传递到Menu component`Object.is,它们被识别为不同的对象。
然而,尽管有这些观察,我仍然很难完全理解手头的问题。看来我可能忽略了一些关键的东西在我的理解。

blmhpbnm

blmhpbnm1#

您应该使用数组来存储将填充组件的数据,而不是在数组中设置子组件。使用一个小的CSS技巧,即使你在组件之间循环,你也可以保留你的数据:

import { useState } from "react";

// no need to set your children components here
const menuInfos = [
  { title: "aaaaaa" },
  { title: "bbbbbb" },
  { title: "cccccc" },
  { title: "dddddd" },
  { title: "eeeeee" }
];

function Area({ title }) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>current area : {title}</h2>
      <div>
        <button
          type="button"
          onClick={() => setCount((current) => current + 1)}
        >
          +1
        </button>
        <div>count : {count}</div>
      </div>
    </div>
  );
}

function Menu({ menuInfos }) {
  const [current, setCurrent] = useState(menuInfos[0].title);

  return (
    <div>
      <div>
        <div>
          {menuInfos.map((info) => (
            <button
              key={info.title}
              style={{
                background: info.title === current ? "red" : ""
              }}
              onClick={() => setCurrent(info.title)}
            >
              {info.title}
            </button>
          ))}
        </div>
      </div>

      {/** render here your components */}
      {menuInfos.map((info, index) => (
        <div
          key={index}
          {/** here's the trick to keep your data between changes of menus */}
          style={{
            display: info.title === current ? "block" : "none"
          }}
        >
          <Area title={info.title} />
        </div>
      ))}
    </div>
  );
}

export default function Index() {
  return (
    <div>
      <Menu menuInfos={menuInfos} />
    </div>
  );
}

此外,here是一个可用的codesandbox示例

wtlkbnrh

wtlkbnrh2#

React在渲染过程中使用了一个名为key的属性,所以它不会更新不需要更新的东西。如果一个父对象的子对象的key和以前的一样,那么它将进行优化的、简化的渲染,这显然意味着{count}的更新不会被完成。这是一个不同的组件,但React不知道它。
添加key属性将使您的代码工作:

const menuInfos = [
  { title: "aaaaaa", children: <Area key="a" title="aaaaaa" /> },
  { title: "bbbbbb", children: <Area key="b" title="bbbbbb" /> },
  { title: "cccccc", children: <Area key="c" title="cccccc" /> },
  { title: "dddddd", children: <Area key="d" title="dddddd" /> },
  { title: "eeeeee", children: <Area key="e" title="eeeeee" /> }
];

**更新:**代码“工作”,但可能不是你想要的...因为一次只渲染一个组件,所以其他组件似乎被卸载并丢失了它们的状态。下次再次显示组件时,将再次创建其状态,并再次从0开始。

tyg4sfes

tyg4sfes3#

我会对你完全诚实:我不能完全解释,为什么你的解决方案不起作用。我最好的猜测是,你的“孩子”并不是真正独立的Area示例,而是同一个示例,只是标题不同。
我重写了你的元素Map,使它按预期工作。请尝试一下:

export default function Menu({ menuInfos }) {
  const [current, setCurrent] = useState(menuInfos[0].title);
  return (
    <div>
      <div>
        <div>
          {menuInfos.map((info) => (
            <button
              style={{ background: info.title === current ? "red" : "" }}
              onClick={() => setCurrent(info.title)}
            >
              {info.title}
            </button>
          ))}
        </div>
      </div>
      {menuInfos.map(
        (info, index) =>
          info.title === current && (
            <div key={index}>
              <Area title={info.title} />
            </div>
          )
      )}
    </div>
  );
}

正如您所看到的,我复制了menuInfo的Map,创建了真正独立的Area示例,并使用条件语句只显示正确的Area。计数状态现在彼此分离。

o4hqfura

o4hqfura4#

您在Area组件中遇到的共享状态问题是因为useState钩子创建了特定于该组件的每个示例的本地状态。在当前代码中,menuInfos数组创建了Area组件的多个示例,但它们都共享相同的count状态。您需要将key传递给Area的每个组件以唯一地标识它。
您的更新代码:

import { useState } from "react";

export default function Menu({ menuInfos }) {
  const [current, setCurrent] = useState(menuInfos[0].title);
  return (
    <div>
      <div>
        <div>
          {menuInfos.map((info) => (
            <button
              style={{ background: info.title === current ? "red" : "" }}
              onClick={() => setCurrent(info.title)}
            >
              {info.title}
            </button>
          ))}
        </div>
      </div>
      <div>{menuInfos.find((info) => info.title === current).children}</div>
    </div>
  );
}

function Area({ title }) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>current area : {title}</h2>
      <div>
        <button type="button" onClick={() => setCount(count + 1)}>
          +1
        </button>
        <div>count : {count}</div>
      </div>
    </div>
  );
}

const menuInfos = [
  { title: "aaaaaa", children: <Area key='a' title="aaaaaa" /> },
  { title: "bbbbbb", children: <Area key='b' title="bbbbbb" /> },
  { title: "cccccc", children: <Area key='c' title="cccccc" /> },
  { title: "dddddd", children: <Area key='d' title="dddddd" /> },
  { title: "eeeeee", children: <Area key='e' title="eeeeee" /> }
];

export function App() {
  return (
    <div>
      <Menu menuInfos={menuInfos} />
    </div>
  );
}

此外,如果你还想保留Area的每个组件的状态,你需要像这样更新你的代码:

import { useState } from "react";

export default function Menu({ menuInfos }) {
  const [current, setCurrent] = useState(menuInfos[0].title);
  return (
    <div>
      <div>
        <div>
          {menuInfos.map((info) => (
            <button
              style={{ background: info.title === current ? "red" : "" }}
              onClick={() => setCurrent(info.title)}
            >
              {info.title}
            </button>
          ))}
        </div>
      </div>
      <div>
        {menuInfos.map((info) => (
          <Area
            key={info.title}
            title={info.title}
          />
        ))}
      </div>
    </div>
  );
}

function Area({ title, chosen }) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>current area : {title}</h2>
      <div>
        <button type="button" onClick={() => setCount(count + 1)}>
          +1
        </button>
        <div>count : {count}</div>
      </div>
    </div>
  );
}

export function App() {
  const menuInfos = [
    { title: "aaaaaa" },
    { title: "bbbbbb" },
    { title: "cccccc" },
    { title: "dddddd" },
    { title: "eeeeee" }
  ];

  return (
    <div>
      <Menu menuInfos={menuInfos} />
    </div>
  );
}

状态在组件之间隔离。React根据组件在UI树中的位置跟踪哪个状态属于哪个组件。可以控制何时保留状态以及何时在重新渲染之间重置状态。
UI树浏览器使用许多树结构来建模UI。DOM表示HTML元素,CSSOM对CSS做同样的事情。甚至还有一个可访问性树!
React还使用树结构来管理和建模您创建的UI。React从你的JSX创建UI树。然后React DOM更新浏览器DOM元素以匹配UI树。在这种情况下,UI树也能够保留每个Area组件的状态,因为它们在UI树中的位置是唯一的。
有用资源:https://dev.to/muneebkhan4/how-react-preserve-and-reset-state-38la

72qzrwbm

72qzrwbm5#

解决方案是确保所有子元素具有不同的位置。
这一更改将使您的代码工作:

<div>
   {menuInfos.map((info) => info.title === current && info.children)}
</div>

为什么?因为返回一个数组,例如。[false, false, <Area />],可见区域元素位于第三个位置。React使用类型和位置来确定哪个元素使用哪个状态。
但是,当您在Area元素之间切换时,它们将被卸载,每次都重置它们的状态。
你可以通过将它们都放在DOM中并在视觉上隐藏它们来解决这个问题:

<div>
  {menuInfos.map((info) => (
     <div style={info.title === current ? {} : { display: "none" }}>
        {info.children}
     </div>
   ))}
</div>

相关问题