Javascript 如果父组件发生更改,则阻止子组件卸载和重新装载 问题:

Javascript 如果父组件发生更改,则阻止子组件卸载和重新装载 问题:,javascript,reactjs,Javascript,Reactjs,在下面的代码中,当单击切换父组件A/B按钮时,将卸载组件并再次重新装载,但实际上只有父组件发生了更改 在这种情况下,我如何防止孩子卸载和重新安装? import React, { useState } from "react" const App = () => { const [a, setA] = useState(true) return ( <div> <button onClick={() => s

在下面的代码中,当单击
切换父组件A/B
按钮时,将卸载
组件并再次重新装载,但实际上只有
父组件发生了更改

在这种情况下,我如何防止
孩子
卸载和重新安装?

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
这会奏效的。不幸的是,这限制了我的
父组件的JSX结构相同。换句话说,如果我的
ParentA
ParentB
的JSX结构不同,那么我不知道如何将差异作为道具传递

例如:

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

如果以这种方式定义parentA和parentB,是否仍有可能将这两个组件抽象为一个
ParentGeneric
组件,并将
parentA
parentB
之间的结构差异作为道具传递?

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

对于这个算法问题,有一些通用的解决方案,即生成将一棵树转换为另一棵树的最小操作数。然而,树的复杂性为O(n3),其中n是树中的元素数

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

  • 两种不同类型的元素将生成不同的树。

  • 开发人员可以使用
    道具提示哪些子元素可以在不同的渲染中保持稳定

  • 实际上,这些假设对于几乎所有的实际用例都是有效的

    如果React能够区分不同父母类型的孩子,那么整个调解过程就会大大放缓


    因此,您只能使用建议的解决方案,即使用道具的普通家长。

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

    不过,首先,由于React和DOM的性质,不可能防止孩子们再次卸载和安装。如果希望DOM节点保持不变,那么树结构必须保持不变

    用这个

    {a ? <ParentA><Child /></ParentA> : <ParentB><Child /></ParentB>}
    
    {a?:}
    
    实际上,每次切换激活时,您都会在树中的该级别更改DOM树,即删除和替换节点,根据定义,这意味着卸载和装载

    为了避免挂载和卸载子对象,您必须在每个渲染上保持DOM树结构相同,至少与子对象所在的树一样深

    只需根据切换更改父组件的可见性,然后使用样式适当定位容器,即可实现所需的效果,请参见

    根据您实际想要实现的目标,您可以将
    子组件作为不同实例或同一实例。在需要相同实例的情况下,可以使用
    childInstance
    而不是直接在render函数中将子对象定义为JSX。您将从控制台中看到,子级不会在每个切换上装载和卸载,因为DOM树没有被更改

    --编辑--

    我下面的解决方案似乎有误导性。我现在同意这个帖子中的其他人的意见,即问题中要求的行为无法实现。我对原始答案所做的一切就是阻止装载/卸载运行。不过,每次更改更高层次的布局组件时,父组件的全部内容都会被抛出并从头开始重新提交

    --编辑结束--

    有办法

    React总是装载和卸载替换了其父级的子级(不同的父级类型导致从更改的父级开始重新装载整个家族分支),这是事实

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

  • 首先渲染子(重)组件
  • 子级获取一个组件道具,该道具将作为父布局组件
  • 然后,子对象从自身周围的道具渲染其父对象,将自身设置为其父对象的子对象
  • 父组件是布局组件,无论您选择在何处以何种方式呈现其子组件 例如,根据您提供的代码:

    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;
    
    从“react”导入{useState,Component};
    常量应用=()=>{
    常数[a,setA]=useState(真);
    返回(
    setA((上一个)=>!上一个)}>切换父项A/B
    );
    };
    const ParentA=({children})=>ParentA{children};
    常量ParentB=({children})=>ParentB{children};
    类子扩展组件{
    componentDidMount(){
    log(“子对象确实挂载”);
    }
    组件将卸载(){
    log(“子项将卸载”);
    }
    render(){
    返回(
    小孩
    );
    }
    }
    导出默认应用程序;
    
    工作代码沙盒:


    查看控制台,它是有效的,并且是一种可接受的做法。

    这不是一个技术性的回答,但是有一些重新租用的库可以实现这一点:和

    我最近亲自研究了一下,但它们有些实验性,似乎有很多开销需要担心。如果你真的不需要移动,@deckele的答案看起来是一个更好的解决方案
    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;