【问题标题】:Prevent child component from unmounting and remounting if parent component changes如果父组件更改,防止子组件卸载和重新安装
【发布时间】:2019-12-18 19:29:37
【问题描述】:

问题:

在下面的代码中,当单击Toggle ParentA/B 按钮时,<Child /> 组件将被卸载并再次重新安装,尽管实际上只有Parent 组件发生了变化。

如何防止Child在这种情况下卸载和重新安装?

import React, { useState } from "react"

const App = () => {
    const [a, setA] = useState(true)
    return (
        <div>
            <button onClick={() => setA(!a)}>Toggle ParentA/B</button>
            {a ? <ParentA><Child /></ParentA> : <ParentB><Child /></ParentB>}
        </div>
    )
}
const ParentA = ({ children }) => <div>parentA{children}</div>
const ParentB = ({ children }) => <div>parentB{children}</div>

class Child extends React.Component {
    componentDidMount() {
        console.log('child did mount')
    }

    componentWillUnmount() {
        console.log('child will unmount')

    }

    render() {
        return (
            <div>child</div>
        )
    }

}

export default App

回复可能的答案:

为什么不让 Child 组件重新挂载,因为它只是一个 &lt;div&gt; 元素?

通常,如果Child 组件的渲染成本很低,这无关紧要。但是,在我的情况下,Child 组件需要很长时间才能挂载,所以每次Parent 组件更改时我都无法重新挂载。

您可以将parentAparentB 字符串作为道具传递给通用父组件。

const App = () => {
    const [a, setA] = useState(true)
    return (
        <div>
            <button onClick={() => setA(!a)}>Toggle ParentA/B</button>
            <ParentGeneric content={a? 'parentA': 'parentB'}><Child /></ParentGeneric>
        </div>
    )
}
const ParentGeneric = ({children, content}) => <div>{content}{children}</div>

class Child extends React.Component {
    ...
}

这会奏效。不幸的是,这将我的Parent 组件的 JSX 结构限制为相同。换句话说,如果我的ParentAParentB的JSX结构不同,那我不知道如何将差异作为props传递。

例如:

const ParentA = ({ children }) => <div><div><h1>parentA{children}</h1></div></div>
const ParentB = ({ children }) => <div>parentB{children}</div>

如果这样定义parentA和parentB,是否仍然可以将这两个组件抽象为ParentGeneric组件,并简单地将ParentAParentB之间的结构差异作为props传递?

【问题讨论】:

  • 给子组件一个静态key prop?
  • @rrd 你的意思是&lt;div key="child"&gt;child&lt;/div&gt;?我试过了,但没有用。
  • 看起来像一个 a/b 问题。如果您的子组件需要很长时间才能安装,那么您可能需要修复一些问题。也许使用 react.Context 将昂贵的瓶颈逻辑在组件树中向上移动。

标签: javascript reactjs


【解决方案1】:

-- 编辑--

似乎我的以下解决方案具有误导性。我现在同意此线程中的其他人的意见,即无法实现问题中所要求的行为。我对原始答案所做的只是阻止挂载/卸载运行。尽管如此,每次更改上层布局组件时,父组件的全部内容都会被丢弃并从头开始重新渲染。

-- 编辑结束--

有办法!

确实,React 的 reconciliation algorithm 将始终挂载和卸载已替换父代的子代(不同的父代类型会导致整个家族分支从更改的父代开始重新挂载)。

但是,您仍然可以将“子”组件显示为某些父布局组件的子组件,而不是在父类型更改时挂载/卸载。你只需要改变组件树的顺序,利用渲染道具模式:

  1. 首先呈现子(重)组件。
  2. Child 获得一个组件 prop,它将成为父布局组件。
  3. 然后子级从 自身周围的道具渲染其父级,将自己设置为其父级的子级。
  4. Parent 是布局组件,无论您在哪里选择,都可以渲染它的子组件!

示例,基于您提供的代码:

import { useState, Component } from "react";

const App = () => {
  const [a, setA] = useState(true);
  return (
    <div>
      <button onClick={() => setA((prev) => !prev)}>Toggle ParentA/B</button>
      <Child layout={a ? ParentA : ParentB} />
    </div>
  );
};
const ParentA = ({ children }) => <div>parentA{children}</div>;
const ParentB = ({ children }) => <div>parentB{children}</div>;

class Child extends Component {
  componentDidMount() {
    console.log("child did mount");
  }

  componentWillUnmount() {
    console.log("child will unmount");
  }

  render() {
    return (
      <this.props.layout>
        <div>child</div>
      </this.props.layout>
    );
  }
}

export default App;

工作代码沙盒:https://codesandbox.io/s/change-parent-no-unmounted-child-nnek0?file=/src/App.js

查看控制台,它可以工作,并且是一种可接受的做法。

【讨论】:

  • 如果您在Childthat component will be remounted 中使用另一个组件而不是div。是否有解决方法,或者此解决方案仅在您使用本机元素而不是用户定义的组件时才有效?
  • @Shepmaster 这是一个很好的观点!看来我的解决方案具有误导性,我将更新答案。显然这不能以任何方式完成,用户定义或使用本机元素。我用原始答案实现的所有目标都是阻止挂载/卸载运行。尽管如此,每次更改较高的布局组件时,父组件的全部内容都会被丢弃并从头开始重新渲染。
  • 谢谢。这很不幸;我将不得不做一些丑陋的黑客来尝试修复我原来的案例......
【解决方案2】:

这并没有太多的技术答案,但有 可以实现此目的的重新父级库:react-reverse-portalreact-reparenting

我最近自己研究了它,但它们有点实验性,似乎需要担心很多开销。如果您实际上不需要跨多个分支移动组件,@deckele 的答案似乎是一个更好的解决方案。

【讨论】:

    【解决方案3】:

    如果不了解此问题的背景,以及您要实现的更大目标,很难提供具体的解决方案。

    但是,首先,由于 React 和 DOM 的性质,不可能阻止子级卸载和再次安装。如果您希望 DOM 节点留在那里,那么树结构必须保持不变。

    通过使用这个

    {a ? <ParentA><Child /></ParentA> : <ParentB><Child /></ParentB>}
    

    每次切换激活时,您实际上都在更改树中该级别的 DOM 树,即您正在删除和替换节点,根据定义,这意味着卸载和安装。

    为避免子项的挂载和卸载,您必须在每次渲染时保持 DOM 树结构相同,至少与子项所在的树一样深。

    您可以实现您想要的效果,只需根据切换更改父组件的可见性,然后使用样式来适当地定位容器,见https://codesandbox.io/s/adoring-hawking-61y66?file=/src/App.js

    根据您实际想要实现的目标,您可以将Child 组件作为不同的实例或相同的实例。如果您想要相同的实例,您可以使用childInstance,而不是直接在渲染函数中将子代定义为 JSX。您会从控制台看到,孩子不会在每次切换时挂载和卸载——因为 DOM 树没有被更改!

    【讨论】:

      【解决方案4】:

      这是不可能的,并且明确定义here(强调我的):

      对于生成将一棵树转换为另一棵树的最少操作数的算法问题,有一些通用解决方案。但是,state of the art algorithms 的复杂度约为 O(n3),其中 n 是树中元素的数量。

      如果我们在 React 中使用它,显示 1000 个元素将需要大约 10 亿次比较。这太贵了。相反,React 基于两个假设实现了启发式 O(n) 算法:

      1. 两种不同类型的元素会产生不同的树。

      2. 开发人员可以使用key 属性提示哪些子元素在不同的渲染中可能是稳定的。

      在实践中,这些假设几乎适用于所有实际用例。

      如果 React 能够区分不同父类型的子级,它会大大减慢整个调解过程。

      因此,您只剩下建议的带有道具的通用父级解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-14
        • 2017-07-10
        • 2021-02-27
        • 2019-10-01
        • 2019-09-30
        • 1970-01-01
        • 2018-10-18
        • 2018-12-01
        相关资源
        最近更新 更多