当父状态改变时组件卸载 [英] Component unmounts when parent state changes

查看:33
本文介绍了当父状态改变时组件卸载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的是 React 16.8.2,每当应用组件中的状态发生变化时,我的组件的子组件都会卸载.

场景如下:

  • 我有 App.jsx(一个功能组件)和许多状态变量(useState)
  • 其中一些状态变量的设置器通过上下文提供程序(后代中的 useContext)沿树向下传递
  • 我有一个菜单组件(应用程序的后代),它调用这些设置器来(例如)显示一个模态对话框
  • 我有一个模态对话框组件(App 的子组件),它使用状态变量作为属性来确定它是否打开——我认为是标准的 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);};//...返回 (<AppContext.Provider value={appControl}>...一堆东西在任何地方都没有使用 foo...包括,在内心深处:<菜单/></AppContext.Provider>);};

在 Menu.jsx 中:

const Menu = props =>{const appControl = useContext(AppContext);//...返回 (... 超级简化测试<div onClick={appControl.toggleFoo}>切换 Foo

);};

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

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

感谢您对我可能做错了什么或误解的任何见解.

解决方案

已解决.这是一个有趣的.这是发生的事情.

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

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

create-react-app 测试光盘测试

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

import React, { useState, useEffect } from "react";const 菜单 = 道具 =><div onClick={props.click}>Toggle Foo</div>;const 测试 = 道具 =>{useEffect(() => {console.log("挂载");返回 () =>console.log("卸载");}, []);返回空;};const App = 道具 =>{const [foo, setFoo] = useState(false);//这是问题的根源//将此行移出函数体//并且它正确安装/卸载const AppCore = 道具 =><测试/>;返回 (<><菜单点击={()=>setFoo(!foo)}/><AppCore/></>);};导出默认应用程序;

然后 npm start,当您点击Toggle Foo"时,您会看到 Test 组件已卸载/重新安装.

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

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

I'm using React 16.8.2, and I'm having a problem with children of my component unmounting whenever state is changed in the app component.

Here's the scenario:

  • I have App.jsx (a functional component) with a number of state variables (useState)
  • The setters for some of these state variables are passed down the tree through a Context provider (useContext in the descendent)
  • I have a menu component (descendent of app), that invokes these setters to (for example) show a modal dialog
  • I have a modal dialog component (child of App), that uses the state variable as a property to determine whether it is open or not -- standard React stuff I think.

My problem: when any state variables in App are changed (through hooks of course), the children of App are unmounted and remounted- even if they have no connection to the state being changed. They aren't just re-rendered - the children are unmounted and their state is re-initialized. So the fields are cleared on my dialog, when they shouldn't be, for example.

This is already a fairly complex application, so I've spent a lot of time today isolating the problem. I then set up a simple create-react-app to try to replicate this behavior there - but this test app behaves like it should. Changing parent state, whether through a prop callback, or through a context-provided callback from the child - re-renders but does not unmount/remount and child state remains intact.

But in my real app, the components re-mount and child state gets re-initialized.

I've simplified it down to the barest that I can - I'm setting a fake state variable "foo" with "setFoo" through the Context from the child. Even though foo is not used by any component, changing the value of foo causes the children of App to unmount/remount.

In 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>
  );
};

In Menu.jsx:

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

If I understand state properly, I do believe that changing state should result in children being re-rendered, but not re-mounted. This is what I'm seeing in my simple create-react-app test, but not in my real app.

I do see that I'm not on the latest React - perhaps upgrading will fix this?

Thanks for any insight on what I may be doing wrong, or misunderstanding here.

解决方案

Solved. This is an interesting one. Here's what happened.

In my App component, I had a fairly deep tree of HOC's. Due to some dubious decisions on my part, I ended up breaking App into two components. App and AppCore. I had a reason for it, and it seemed to make sense at 3am. But to be both quick and dirty, I stuck AppCore as a const, inside my App function. I remember thinking to myself "I wonder what problems this will cause?" Now I know. Perhaps a React expert can fully explain this one to me though, as I don't see the difference between JSX assigned to a constant, and JSX returned directly. But there clearly is, and this is simple to reproduce.

To reproduce, create-react-app a test app:

create-react-app test
cd test

Then replace the contents of App.js with:

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;

Then npm start, and when you click on "Toggle Foo" you'll see that the Test component is unmounted/remounted.

The solution here, is to simply move AppCore out of the function body. In my real app, this means I have some refactoring to do.

I wonder if this would be considered a React issue?

这篇关于当父状态改变时组件卸载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆