[React 进阶系列] React Portal 案例学习

[React 进阶系列] React Portal 案例学习

    • 什么是 Portal
    • 使用 Portal
    • 事件冒泡
    • Context 的使用
    • 参考资料

最近看文档/教程/资料,偶然间看到了一个以前没有碰到的东西:Portal。本着学习的精神打开了文档一看,才发现 Portal 的存在真的解决了一些以前碰到的问题。

什么是 Portal

Portal 是 ReactDOM 在 v16 的时候新推出的一个特性,到目前来说已经有 5 年的特性。它的优点有这几个:

  1. 可以在虚拟 DOM 树之外挂载 React 结点
  2. 挂在的 React 结点虽然物理地址在虚拟 DOM 树之外,但是其父组件可以监听到该组件的事件冒泡
  3. React Context 可以完整地保留

使用 Portal

官方文档上举例 Portal 的应用场景,包括:dialogs(对话框), hovercards(悬浮卡片), and tooltips(提示工具)。

下面就是一个 Modal 的简单的案例。

import "./styles.css";

const Modal = () => {
  return <div className="modal">Modal</div>;
};

export default function App() {
  return (
    <div className="App">
      <div className="modal__wrapper">
        <Modal />
      </div>
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <h3>What happen here</h3>
      <h4>something happen here</h4>
    </div>
  );
}

其渲染结果如下:

[React 进阶系列] React Portal 案例学习_第1张图片

基本上可以满足我的需求。

但是,有的时候业务逻辑是需要在组件中进行条件渲染,这个时候 Modal 的定位可能就会出现问题,如:

[React 进阶系列] React Portal 案例学习_第2张图片

以及

[React 进阶系列] React Portal 案例学习_第3张图片

诚然,再创建一个新的 backdrop,定位高度宽度为 100vh,让其背景色为透明, position 设置为 relative,再继续使用 CSS 对 Modal 进行位置的调控也能暴力解决问题(惭愧……这就是之前我为了快速推进度而采取的解决方法)。不过这样粗暴的解决方法也有一定的问题:

  1. 用户不关闭当前 Modal 之前是无法与背景上的元素进行任何的交互

  2. 语义化的结构也不对

这个时候,使用 Portal 就可以解决这个问题,如:

[React 进阶系列] React Portal 案例学习_第4张图片

对 div 进行位置上的改变:

[React 进阶系列] React Portal 案例学习_第5张图片

渲染效果还是完全一样的。

这是因为 Portal 被挂载的元素在元素 id 为 modal 的结点上,而 modal 所处的位置在:

<body>
  <noscript> You need to enable JavaScript to run this app. noscript>
  <div id="modal">div>
  <div id="root">div>
body>

对渲染的 HTML 结果进行分析,也会发现 Modal 组件被挂载到了 #modal 结点上:

[React 进阶系列] React Portal 案例学习_第6张图片

事件冒泡

现在将代码少许更新一下:

import "./styles.css";
import ReactDOM from "react-dom";

const Modal = ({ clickHandler }) => {
  return (
    <div className="modal">
      <button onClick={clickHandler}>btn</button>
    </div>
  );
};

export default function App() {
  const clickHandler = () => {
    console.log("hello world");
  };

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <h3>What happen here</h3>
      <h4>something happen here</h4>
      <div className="modal__wrapper">
        {ReactDOM.createPortal(
          <Modal clickHandler={clickHandler} />,
          document.querySelector("#modal")
        )}
      </div>
    </div>
  );
}

这里也模拟了一些使用 Modal 会经常碰到的情况,也就是事件处理。在使用 Portal 的情况下,App 这个父组件是可以将事件处理函数传送到 Modal 中:

[React 进阶系列] React Portal 案例学习_第7张图片

同样的情况下,onChange 之类的事件监听也是可以触发,从而修改父组件中的状态,或是复用父组件中的事件处理函数。

Context 的使用

我会加上这一点其实是因为这个 Stack Overflow 的回答:How to use ReactDOM.createPortal() in React 16?,其中得分最高的回复人说到:

I’m emphasizing on it because very popular libraries like react-router, redux heavily uses the react context. So context availability when using Portal is very helpful.

如 react-router, redux 的第三方库是 React Context 的重度依赖者,因此想要最大程度地享用这些库的特性,也需要保留 React Context 的内容。

参考资料

  • How to use ReactDOM.createPortal() in React 16?
  • Portals

你可能感兴趣的:(#,React,react.js)