parent 状态更改时组件卸载
Component unmounts when parent state changes
我使用的是 React 16.8.2,当应用程序组件中的状态发生更改时,我的组件 children 卸载时遇到问题。
场景如下:
- 我有App.jsx(一个功能组件)和一些状态变量(useState)
- 其中一些状态变量的设置器通过上下文提供程序(后代中的 useContext)向下传递到树中
- 我有一个菜单组件(应用程序的后代),它调用这些设置器以(例如)显示模态对话框
- 我有一个模态对话框组件(App 的 child),它使用状态变量作为 属性 来确定它是否打开——我认为是标准的 React 东西。
我的问题:当 App 中的任何状态变量发生变化时(当然是通过钩子),App 的 children 将被卸载并重新装载 - 即使它们与正在更改的状态没有任何联系。它们不仅仅是 re-rendered - children 已卸载,它们的状态是 re-initialized。因此,我的对话框中的字段在不应该被清除的时候被清除了,例如。
这已经是一个相当复杂的应用程序,所以我今天花了很多时间来隔离问题。然后我设置了一个简单的 create-react-app 来尝试在那里复制这种行为 - 但这个测试应用程序的行为与它应该的一样。改变 parent 状态,无论是通过 prop 回调,还是通过 child - re-renders 的 context-provided 回调,但不是 unmount/remount 和 child状态保持不变。
但在我的真实应用中,组件 re-mount 和 child 状态变为 re-initialized。
我已将其尽可能简化 - 我正在通过来自 child 的上下文设置一个带有 "setFoo" 的假状态变量 "foo"。即使 foo 没有被任何组件使用,更改 foo 的值也会导致 App 的 children 变为 unmount/remount.
在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>
);
};
如果我正确理解状态,我相信改变状态应该导致 children 被 re-rendered,而不是 re-mounted。这是我在我的简单 create-react-app 测试中看到的,但不是在我的真实应用程序中看到的。
我确实看到我没有使用最新的 React - 也许升级会解决这个问题?
感谢您对我可能做错的地方或此处的误解的任何见解。
已解决。这是一个有趣的。事情是这样的。
在我的 App 组件中,我有一个相当深的 HOC 树。由于我做出了一些可疑的决定,我最终将 App 分成了两个部分。应用程序和 AppCore。我这样做是有原因的,而且在凌晨 3 点似乎是有道理的。但是为了快速 和 脏,我将 AppCore 作为常量固定在我的 App 函数中。我记得自己在想 "I wonder what problems this will cause?" 现在我知道了。不过,也许 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组件是unmounted/remounted。
此处的解决方案是简单地将 AppCore 移出函数体。在我的真实应用中,这意味着我需要进行一些重构。
我想知道这是否会被视为 React 问题?
我使用的是 React 16.8.2,当应用程序组件中的状态发生更改时,我的组件 children 卸载时遇到问题。
场景如下:
- 我有App.jsx(一个功能组件)和一些状态变量(useState)
- 其中一些状态变量的设置器通过上下文提供程序(后代中的 useContext)向下传递到树中
- 我有一个菜单组件(应用程序的后代),它调用这些设置器以(例如)显示模态对话框
- 我有一个模态对话框组件(App 的 child),它使用状态变量作为 属性 来确定它是否打开——我认为是标准的 React 东西。
我的问题:当 App 中的任何状态变量发生变化时(当然是通过钩子),App 的 children 将被卸载并重新装载 - 即使它们与正在更改的状态没有任何联系。它们不仅仅是 re-rendered - children 已卸载,它们的状态是 re-initialized。因此,我的对话框中的字段在不应该被清除的时候被清除了,例如。
这已经是一个相当复杂的应用程序,所以我今天花了很多时间来隔离问题。然后我设置了一个简单的 create-react-app 来尝试在那里复制这种行为 - 但这个测试应用程序的行为与它应该的一样。改变 parent 状态,无论是通过 prop 回调,还是通过 child - re-renders 的 context-provided 回调,但不是 unmount/remount 和 child状态保持不变。
但在我的真实应用中,组件 re-mount 和 child 状态变为 re-initialized。
我已将其尽可能简化 - 我正在通过来自 child 的上下文设置一个带有 "setFoo" 的假状态变量 "foo"。即使 foo 没有被任何组件使用,更改 foo 的值也会导致 App 的 children 变为 unmount/remount.
在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>
);
};
如果我正确理解状态,我相信改变状态应该导致 children 被 re-rendered,而不是 re-mounted。这是我在我的简单 create-react-app 测试中看到的,但不是在我的真实应用程序中看到的。
我确实看到我没有使用最新的 React - 也许升级会解决这个问题?
感谢您对我可能做错的地方或此处的误解的任何见解。
已解决。这是一个有趣的。事情是这样的。
在我的 App 组件中,我有一个相当深的 HOC 树。由于我做出了一些可疑的决定,我最终将 App 分成了两个部分。应用程序和 AppCore。我这样做是有原因的,而且在凌晨 3 点似乎是有道理的。但是为了快速 和 脏,我将 AppCore 作为常量固定在我的 App 函数中。我记得自己在想 "I wonder what problems this will cause?" 现在我知道了。不过,也许 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组件是unmounted/remounted。
此处的解决方案是简单地将 AppCore 移出函数体。在我的真实应用中,这意味着我需要进行一些重构。
我想知道这是否会被视为 React 问题?