使用 React Portals 将子元素渲染到 DOM 层级结构外部

Avatar of Kingsley Silas
Kingsley Silas

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

假设我们需要将子元素渲染到 React 应用程序中。很容易吧?该子元素会被挂载到最近的 DOM 元素上,并因此在其中渲染。

render() {
  return (
    <div>
      // Child to render inside of the div
    </div>
  );
}

但是!如果我们想将该子元素渲染到 div 外部 的其他地方呢?这可能很棘手,因为它打破了组件需要渲染为新元素并遵循父子层级结构的约定。父元素希望跟随其子元素。

这就是 React Portals 的用武之地。它们提供了一种在 DOM 层级结构外部渲染元素的方法,使元素更具可移植性。这可能不是一个完美的类比,但 Portals 有点像马里奥兄弟中的管道,可以将您从游戏的正常流程中传送到不同的区域。

Portals 的妙处在于?即使它们触发了独立于子元素父元素的自身事件,父元素仍在监听这些事件,这对于跨应用程序传递事件很有用。

我们将在本文中一起创建一个 Portal,然后将其转换为可重用组件。让我们开始吧!

我们正在构建的示例

这是一个关于 Portal 实际应用的相对简单的示例

查看 Kingsley Silas Chijioke 在 CodePen 上发布的笔 React Portal@kinsomicrote)。

切换元素的可见性并不是什么新鲜事。但是,如果您仔细查看代码,您会注意到输出的元素受按钮控制,即使它不是按钮的直接后代。实际上,如果您将源代码与 DevTools 中的渲染输出进行比较,您将看到它们之间的关系。

因此,输出元素的父元素实际上会监听按钮点击事件,并允许插入子元素,即使它和按钮在 DOM 中是独立的同级元素。让我们分解创建此切换 Portal 元素的步骤,以了解其工作原理。

步骤 1:创建 Portal 元素

React 应用程序的第一行将告诉您 App 元素使用 ReactDOM 渲染在文档根目录上。如下所示;

ReactDOM.render(<App />, document.getElementById("root"));

我们需要将 App 元素放置在 HTML 文件中才能执行它。

<div id="App"></div>

Portals 也是类似的。创建 Portal 的第一步是在 HTML 文件中创建一个新的 div 元素。

<div id="portal"></div>

此 div 将用作我们的目标。我们使用 #portal 作为 ID,但它不必是那个。在此目标 div 内渲染的任何组件都将保持 React 的上下文。我们需要将 div 存储为变量的值,以便我们可以使用我们将创建的 Portal 组件。

const portalRoot = document.getElementById("portal");

看起来很像执行 App 元素的方法,对吧?

步骤 2:创建 Portal 组件

接下来,让我们将 Portal 设置为组件。

class Portal extends React.Component {
  constructor() {
    super();
    // 1: Create a new div that wraps the component
    this.el = document.createElement("div");
  }
  // 2: Append the element to the DOM when it mounts
  componentDidMount = () => {
    portalRoot.appendChild(this.el);
  };
  // 3: Remove the element when it unmounts
  componentWillUnmount = () => {
    portalRoot.removeChild(this.el);
  };
  render() {
    // 4: Render the element's children in a Portal
    const { children } = this.props;
    return ReactDOM.createPortal(children, this.el);
  }
}

让我们退一步,看看这里发生了什么。

我们在构造函数中创建一个新的 div 元素,并将其设置为 this.el 的值。当 Portal 组件挂载时,this.el 会作为子元素附加到 HTML 文件中我们添加它的那个 div。在我们的例子中,就是 <div id="portal"></div> 行。

DOM 树将如下所示。

<div> // Portal, which is also portalRoot
  <div> // this.el
  </div>
</div>

如果您不熟悉 React 并且对挂载和卸载元素的概念感到困惑,Jake Trent 给出了很好的解释。简而言之:挂载是元素插入 DOM 的时刻。

当组件卸载时,我们希望删除子元素以避免任何内存泄漏。我们将此 Portal 组件导入到另一个组件中以供使用,该组件是我们示例中包含标题和按钮的 div。这样做时,我们将 Portal 组件的子元素也一起传递。这就是为什么我们有 this.props.children 的原因。

步骤 3:使用 Portal

要渲染 Portal 组件的子元素,我们使用 ReactDOM.createPortal()。这是一个特殊的 ReactDOM 方法,它接受子元素和我们创建的元素。要了解 Portal 的工作原理,让我们在 App 组件中使用它。

但是,在我们这样做之前,让我们先了解一下我们希望 App 如何运行的基本知识。当 App 加载时,我们希望显示文本和按钮——然后我们可以切换按钮以显示或隐藏 Portal 组件。

class App extends React.Component {
  // The initial toggle state is false so the Portal element is out of view
  state = {
    on: false
  };

  toggle = () => {
    // Create a new "on" state to mount the Portal component via the button
    this.setState({
      on: !this.state.on
    });
  };
  // Now, let's render the components
  render() {
    const { on } = this.state;
    return (
      // The div where that uses the Portal component child
      <div>
        <header>
          <h1>Welcome to React</h1>
        </header>
        <React.Fragment>
          // The button that toggles the Portal component state
          // The Portal parent is listening for the event
          <button onClick={this.toggle}>Toggle Portal</button>
          // Mount or unmount the Portal on button click
          <Portal>
            {
              on ?
                <h1>This is a portal!</h1>
              : null
            }
          </Portal>
        </React.Fragment>
      </div>
    );
  }
}

由于我们希望打开和关闭 Portal,因此我们需要使用 组件状态 来管理切换。这基本上是一种方法,可以在点击事件上将 on 的状态设置为 truefalse。当 on 为 true 时,Portal 会被渲染;否则,我们什么也不渲染。

on 状态设置为 true 时,DOM 如下所示。

onfalse 时,Portal 组件不会在根目录中渲染,因此 DOM 如下所示。

更多用例

模态框是 Portal 的完美候选者。实际上,React 文档使用它作为 Portal 工作原理的主要示例。

查看 Dan Abramov 在 CodePen 上发布的笔 示例:Portals@gaearon)。

它与概念相同,其中创建了一个 Portal 组件,并使用状态将子元素附加到 Modal 组件。

我们甚至可以将来自外部源的数据插入模态框中。在此示例中,App 组件列出了使用 axios 从 API 获取的用户。

查看 Kingsley Silas Chijioke 在 CodePen 上发布的笔 React Portal 3@kinsomicrote)。

工具提示怎么样?David Gilberston 有一个不错的演示。

查看 David Gilbertson 在 CodePen 上发布的笔 React Portal Tooptip@davidgilbertson)。

J Scott Smith 展示了如何使用 Portals 来跳出定位。

他还有一个精巧的示例,演示了插入元素管理状态。

总结

就是这样!希望这能为您提供关于 Portal 的坚实基础理解,包括它们是什么、它们做什么以及如何在 React 应用程序中使用它们。这个概念可能看起来微不足道,但能够将元素移动到 DOM 层级结构之外是一种方便的方法,可以使组件更具扩展性和可重用性……所有这些都指向使用 React 的核心优势。

更多信息