显示/隐藏 ReactJS 组件而不会丢失其内部状态

Show/Hide ReactJS components without losing their internal state?

本文关键字:内部 状态 隐藏 ReactJS 组件 显示      更新时间:2023-09-26

我一直在通过不渲染它们来隐藏/显示反应组件,例如:

render: function() {
  var partial;
  if (this.state.currentPage === 'home') {
    partial = <Home />;
  } else if (this.state.currentPage === 'bio') {
    partial = <Bio />;
  } else {
    partial = <h1>Not found</h1>
  }
  return (
    <div>
      <div>I am a menu that stays here</div>
      <a href="#/home">Home</a> <a href="#/bio">Bio</a>
      {partial}
    </div>
  );
}

但只要说<Bio/>组件有很多内部状态。每次重新创建组件时,它都会丢失其内部状态,并重置为原始状态。

我当然知道我可以将它的数据存储在某个地方,并通过 props 传递它或只是全局访问它,但这些数据实际上并不需要存在于组件之外。我也可以使用CSS(display:none)隐藏/显示组件,但我更愿意像上面一样隐藏/显示它们。

这里的最佳实践是什么?

编辑:也许说明问题的更好方法是使用一个例子:

忽略 React,并假设您只是使用了一个桌面应用程序,该应用程序具有一个名为 A 的选项卡组件的配置对话框,该组件有 2 个选项卡,分别名为 1 和 2。

假设选项卡 A.1 有一个电子邮件文本字段,您填写了电子邮件地址。然后,单击选项卡 A.2 一秒钟,然后单击返回选项卡 A.1。发生了什么事?您的电子邮件地址将不再存在,它将被重置为空,因为内部状态未存储在任何地方。

按照以下答案之一中的建议,内部化状态,但仅适用于组件及其直接子项。如果你的组件任意嵌套在其他组件中,比如选项卡中的选项卡,它们保持内部状态的唯一方法是将其外部化到某个地方,或者使用实际上始终保留所有子组件的display:none方法。

在我看来,这种类型的数据不是您想要弄脏应用程序状态的数据......甚至想甚至不得不考虑。似乎您应该能够在父组件级别控制数据,并选择保留或丢弃,而无需使用display:none方法,也不关心有关如何存储的详细信息。

一种选择是在组件本身内移动条件:

Bio = React.createClass({
    render: function() {
        if(this.props.show) {
            return <p>bio comp</p>
        } else {
            return null;
        }
    }
});
<Bio show={isBioPage} />

这是否是"最佳实践"可能取决于具体情况。

不幸的是,style={{display: 'none'}}技巧仅适用于普通的 DOM 元素,而不适用于 React 组件。我必须将组件包装在div 中。所以我不必将状态级联到子组件。

<div className="content">
  <div className={this.state.curTab == 'securities' ? 'active' : ''}>
    <Securities />
  </div>
  <div className={this.state.curTab == 'plugins' ? 'active' : ''}>
    <Plugins />
  </div>
</div>

看起来官方文档建议将有状态的孩子隐藏在style={{display: 'none'}}

这里的根本问题是,在 React 中,你只被允许将组件挂载到它的父级,这并不总是期望的行为。但是如何解决这个问题呢?

我提出了解决此问题的解决方案。更详细的问题定义、src 和示例可以在这里找到:https://github.com/fckt/react-layer-stack#rationale

理由

react/react-dom附带2个基本假设/想法:

  • 每个 UI 都是自然分层的。这就是为什么我们有相互包裹的components的想法
  • 默认情况下,react-dom将(物理)子组件挂载到其父 DOM 节点

问题是有时第二个属性不是你想要的 在你的情况下。有时您希望将组件装载到 不同的物理 DOM 节点和保持之间的逻辑连接 父母和孩子同时。

规范示例是类似工具提示的组件:在某个时候 开发过程你会发现你需要添加一些 UI element的描述:它将以固定图层呈现,并且 应该知道它的坐标(UI element坐标或 鼠标坐标),同时它需要信息是否 现在是否需要显示,其内容和一些上下文来自 父组件。此示例显示有时逻辑层次结构 与物理 DOM 层次结构不匹配。

看看 https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example 看看具体的例子,这是对你的问题的回答(看看"use"属性):

import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
  const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
  return (
    <Cell {...props}>
        // the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
        <Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
            hideMe, // alias for `hide(modalId)`
            index } // useful to know to set zIndex, for example
            , e) => // access to the arguments (click event data in this example)
          <Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
            <ConfirmationDialog
              title={ 'Delete' }
              message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
              confirmButton={ <Button type="primary">DELETE</Button> }
              onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
              close={ hideMe } />
          </Modal> }
        </Layer>
        // this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
        <LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
          <div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
            <Icon type="trash" />
          </div> }
        </LayerContext>
    </Cell>)
// ...