【问题标题】:Component unmounts when parent state changes当父状态改变时组件卸载
【发布时间】:2019-04-10 07:21:44
【问题描述】:

我正在使用 React 16.8.2,并且每当应用组件中的状态发生更改时,我的组件子级卸载都会出现问题。

这是场景:

  • 我有带有许多状态变量 (useState) 的 App.jsx(一个功能组件)
  • 其中一些状态变量的设置器通过上下文提供程序(后代中的 useContext)沿树向下传递
  • 我有一个菜单组件(应用程序的后代),它调用这些设置器来(例如)显示一个模式对话框
  • 我有一个模态对话框组件(App 的子组件),它使用 state 变量作为属性来确定它是否打开——我认为是标准的 React 东西。

我的问题:当 App 中的任何状态变量发生变化时(当然是通过钩子),App 的子级将被卸载并重新安装——即使它们与正在更改的状态没有任何联系。它们不只是重新渲染 - 孩子们被卸载并且他们的状态被重新初始化。因此,例如,当它们不应该被清除时,我的对话框中的字段会被清除。

这已经是一个相当复杂的应用程序,所以我今天花了很多时间来隔离问题。然后我设置了一个简单的 create-react-app 来尝试在那里复制这种行为 - 但是这个测试应用程序的行为就像它应该的那样。更改父状态,无论是通过 prop 回调,还是通过来自子级的上下文提供的回调 - 重新渲染但不会卸载/重新安装,子级状态保持不变。

但在我的真实应用中,组件会重新挂载,子状态会重新初始化。

我已将其简化到最简单的程度 - 我正在通过孩子的上下文设置一个带有“setFoo”的假状态变量“foo”。即使 foo 没有被任何组件使用,更改 foo 的值也会导致 App 的子级卸载/重新安装。

在 App.jsx 中:

const App = props => {
  const [foo, setFoo] = useState(false);
  // ...
  const appControl = {
    toggleFoo: () => setFoo(!foo);
  };
  // ...
  return (
    <AppContext.Provider value={appControl}>
      ... a bunch of stuff not using foo anywhere
      ... including, deep down:
      <Menu />
    </AppContext.Provider>
  );
};

在 Menu.jsx 中:

const Menu = props => {
  const appControl = useContext(AppContext);
  // ...
  return (
    ... super simplified for test
    <div onClick={appControl.toggleFoo}>
      Toggle Foo
    </div>
  );
};

如果我正确理解状态,我相信改变状态应该会导致孩子被重新渲染,但不会被重新安装。这是我在简单的 create-react-app 测试中看到的,但不是在我的真实应用中看到的。

我确实看到我没有使用最新的 React - 也许升级会解决这个问题?

感谢您对我可能做错了什么或这里的误解提供任何见解。

【问题讨论】:

  • 目前您的问题中的代码中没有任何内容可以解释为什么要卸载子组件。请创建一个Minimal, Complete, and Verifiable example,以便有人可以更轻松地帮助您。
  • 您是否在某些已卸载/重新安装的子级或它们的某些中间父级中使用key 属性?如果发生变化,则会导致重新安装。
  • @Tholle - 我要看看我是否可以做一个最小的、可重复的测试。如果我这样做了,那很可能会告诉我问题出在哪里......很高兴知道,根据您在此处看到的内容,您同意不应重新安装组件。至少我知道我理解正确。我已经用 16.8.2 测试了我的 create-react-app 测试,它仍然可以正常工作。我希望这只是一个 React 错误 =)
  • @cbdev420 我没有在 App 的任何直接子级上使用密钥。树中有一些用地图渲染的地方确实使用了键。
  • 这可能是你的问题。这是我能想到 React 重新安装东西的唯一方法。第二个想法是route 的一些变化,但从你描述的情况来看,情况似乎并非如此。仔细研究一下,和/或在您的问题上粘贴更多代码,以便我们也可以查看。

标签: reactjs react-hooks


【解决方案1】:

解决了。这是一个有趣的。这就是发生的事情。

在我的 App 组件中,我有一个相当深的 HOC 树。由于我的一些可疑决定,我最终将 App 分成两个组件。应用程序和应用程序核心。我这样做是有原因的,而且凌晨 3 点似乎很有意义。但为了既快速 和 脏,我将 AppCore 作为一个常量,放在我的 App 函数中。我记得自己在想“我想知道这会导致什么问题?”现在我明白了。不过,也许 React 专家可以向我充分解释这一点,因为我看不出 JSX 分配给常量和 JSX 直接返回之间的区别。但显然有,而且这很容易重现。

要重现,create-react-app 一个测试应用:

create-react-app test
cd test

然后将App.js的内容替换为:

import React, { useState, useEffect } from "react";

const Menu = props => <div onClick={props.click}>Toggle Foo</div>;

const Test = props => {
  useEffect(() => {
    console.log("mounted");
    return () => console.log("unmounted");
  }, []);
  return null;
};

const App = props => {
  const [foo, setFoo] = useState(false);

  // this is the root of the problem
  // move this line outside of the function body
  // and it mounts/unmounts correctly
  const AppCore = props => <Test />;

  return (
    <>
      <Menu click={() => setFoo(!foo)} />
      <AppCore />
    </>
  );
};

export default App;

然后npm start,当你点击“Toggle Foo”时你会看到Test组件被卸载/重新安装。

这里的解决方案是简单地将 AppCore 移出函数体。在我的真实应用中,这意味着我需要进行一些重构。

我想知道这是否会被视为 React 问题?

【讨论】:

  • 原因是每次调用App 函数时,即每次重新渲染App 组件时,您都为AppCore 创建了一个新的lambda。当然,它将与之前渲染的组件没有任何联系,从而强制卸载之前的组件并安装新的组件。
  • 非常感谢您的回答。我们遇到了类似的问题,这个答案对我们帮助很大。
  • 非常感谢,我在组件层次结构的顶部重现了同样的问题,导致嵌套重新渲染问题。
  • @Cerberus 这意味着 useCallback 或 useMemo 可以帮助保持对先前组件的引用。但不知何故,在我的情况下,它仍然重新安装了组件。
  • 非常感谢!!!我快要疯了!
猜你喜欢
  • 2021-06-08
  • 1970-01-01
  • 2018-12-01
  • 2017-07-10
  • 2021-12-10
  • 2021-10-08
  • 1970-01-01
  • 2022-06-13
  • 1970-01-01
相关资源
最近更新 更多