首页 > 解决方案 > 防止组件在 DOM 的不同部分显示时卸载

问题描述

我的应用程序的布局会根据用户做出的一些选择而改变,因此相同的组件会挂在 DOM 中的不同节点下。不幸的是,React 卸载并重新安装了组件。结果,我的组件丢失了已累积的状态。我试图添加一个key属性来传达这是同一个实例的信息,但我得到了相同的结果。下面显然是一个SSCCE。每次用户单击按钮时,组件A都会被卸载并重新安装:

class A extends React.Component {
  componentWillUnmount = () => {
    console.log('map::componentWillUnmount()');
  }

  componentDidMount = () => {
    console.log('map::componentDidMount()');
  }
  render() {
    return (
      <div>
        this is component A
      </div>
    );

  }
}


class App extends React.Component {

  constructor(props) {
    super(props);
    this.state={i:0};
  }

  increment = () => {
    this.setState({i: this.state.i+1});
  }


  render() {
    console.log('app::render');
    if (this.state.i % 2 === 0)
      return (
        <>
        <div>
          <div>
            <A key={42}/>
          </div>
        </div>
        <button onClick={this.increment}>re-render</button>
        </>
      );
    else return (
      <>
      <div>
        <A key={42}/>
      </div>
      <button onClick={this.increment}>re-render</button>
      </>
    )
  }
}

只是为了澄清一下,我的代码除了重现问题之外并没有试图实现任何目标,就像我说的那样,它是一个 SSCCE。当然,在某些应用程序中,用户可以从“首选项”菜单更改布局,以便组件根据用户的偏好最终位于 DOM 中的不同位置。我无法想象在这种情况下失去状态是可以接受的。处理这种情况的正确方法是什么?

标签: javascriptreactjsdom

解决方案


这是因为条件渲染返回两个不同的树结构,其中路径A不同。

  • React.Fragment> div> div>A
  • React.Fragment> div>A


想象一下,如果我们通过使用普通 JS 手动进行安装和卸载来模仿 React,我们将不得不:

  1. 存储<A/>为变量
  2. 移除内部<div/><A/>也会自动移除)
  3. 然后将先前存储的附加<A/>到外部<div/>(这实际上也不是一个好方法,因为这假设<A/>一旦安装它就永远不需要更新自身,即使道具发生变化)

只要有一个 remove 和 append,它就相当于一个 React 组件被卸载然后再次安装。


代码有点模糊,我无法通过在第一个条件下使用 2<div/>秒而在第二个条件下只有 1来判断它试图实现的目标<div/>。但也许您可以使用三元运算符有条件地呈现您需要的内容而无需<A/>理会(并且可能需要一点 CSS 来使内部<div/>看起来好像嵌套在另一个内部<div/>)。这样,在更改状态<A/>时不会卸载。<App/>

return (
  <>
    <div>
      <A />
      {condition ? // where `condition` is a boolean
        <div style={{
          position: 'absolute'
          // and may be other styles to achieve the visual trickery
        }}>
          {/* your content here */}
        </div>
        :
        null
      }
    </div>
    <button onClick={this.increment}>re-render</button>
  </>
)

顺便说一句,他们说 42 是一切的答案,但我认为不适用于这种情况。将密钥设置为 42 将无济于事。


推荐阅读