React 传送门

摘要:在本教程中,您将学习如何使用 React 传送门将子元素渲染到父组件 DOM 层级结构之外的 DOM 节点中。

React 传送门介绍

React 传送门提供了一种将子元素渲染到父组件 DOM 层级结构之外的 DOM 节点中的方法。

在渲染组件到常规 DOM 层级结构之外时,例如模态框、工具提示或下拉菜单,React 传送门很有用。

要创建传送门,可以使用 ReactDOM.createPortal() 函数。createPortal 函数接受两个参数

  • 要渲染的 JSX 内容。
  • 要渲染 JSX 内容的 DOM 节点。

我们将创建一个可重复使用的 React 模态框组件来演示 React 传送门。

创建一个新的 React 项目

首先,打开您的终端并运行此命令以创建一个新的 React 项目

npx create-react-app react-portalsCode language: JavaScript (javascript)

其次,导航到 react-portals 目录。

cd react-portalsCode language: JavaScript (javascript)

第三,运行以下命令启动 React 应用程序

npm startCode language: JavaScript (javascript)

我们将删除 src 目录中的所有文件,并从头开始。

创建模态框的第一版

步骤 1. 创建一个 index.js 文件,该文件在屏幕上显示 App 组件

import ReactDOM from 'react-dom/client';
import App from './App';

const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);

root.render(<App />);Code language: JavaScript (javascript)

步骤 2. 创建 App 组件

import { useState } from 'react';

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

export default App;Code language: JavaScript (javascript)

步骤 3. 创建 Modal 组件

import './Modal.css';

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

export default Modal;Code language: JavaScript (javascript)

步骤 4. 创建一个空的 Modal.css 文件。

步骤 5. 增强 App 组件以显示模态框。

import { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [showModal, setShowModal] = useState(false);

  const handleClick = () => setShowModal(!showModal);

  return (
    <div className="container">
      <button type="button" onClick={handleClick}>
        Open
      </button>
      {showModal && <Modal />}
    </div>
  );
};

export default App;Code language: JavaScript (javascript)

App 组件使用 showModal 状态来显示/隐藏模态框。如果 showModal 为 true,则 Modal 组件将显示;如果为 false,则 Modal 组件将不显示。

App 组件还有一个按钮,当单击该按钮时会打开模态框。当单击事件发生时,我们将 showModal 状态的值从 false 翻转为 true 以显示模态框。

步骤 6. 在模态框中显示内容。

import './Modal.css';

const Modal = () => {
  return (
    <div className="modal-container">
      <div className="modal">
        <h2>Modal is cool </h2>
        <p>This is a modal in React</p>
      </div>
    </div>
  );
};

export default Modal;Code language: JavaScript (javascript)

步骤 7. 将样式添加到 Modal.css 文件

.modal-container {
  /* create an overlay */
  position: absolute;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.5);

  /* center the modal */
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal {
  background-color: white;
  padding: 1rem;
  border-radius: 0.5rem;
}Code language: JavaScript (javascript)

当您单击按钮时,模态框将显示

React Portals - Modal

它之所以有效,是因为我们在 .modal-container 元素中使用了 absolute 位置,并且所有父元素都没有除 static 之外的其他位置。

假设 .container 元素在 Modal.css 文件中将 position 设置为 relative,那么模态框将无法正常工作。

.container {
  position: relative;
}

/* other rules */Code language: JavaScript (javascript)
React Portals - Broken Modal

要解决此问题,我们需要在 body 元素而不是 .container 元素下渲染模态框。为此,我们将使用 React 传送门。

使用 React 传送门

步骤 1. 在 public/index.html 文件中,将一个具有类 .model-wrapper 的 div 直接添加到 body 元素下

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

    <div class="modal-wrapper"></div>
  </body>
...Code language: JavaScript (javascript)

步骤 2. 使用 React 传送门渲染模态框。

使用 ReactDOM.createPortal() 函数在 .modal-wrapper 元素中渲染模态框组件

import ReactDOM from 'react-dom';
import './Modal.css';

const Modal = () => {
  return ReactDOM.createPortal(
   <div className="modal-container">
      <div className="modal">
        <h2>Modal is cool </h2>
        <p>This is a modal in React</p>
      </div>
    </div>,
    document.querySelector('.modal-wrapper')
  );
};

export default Modal;Code language: JavaScript (javascript)

如果您单击打开按钮,模态框将正确显示。

到目前为止,模态框只有静态内容。通常,模态框包括父组件提供的动态内容,并且有一个按钮用于关闭自身。要将这些功能添加到 Modal 组件中,我们可以使用 属性

步骤 3. 增强 App 组件

import { useState } from 'react';
import Modal from './Modal';

const App = () => {
  const [showModal, setShowModal] = useState(false);

  const handleClick = () => setShowModal(!showModal);
  const handleClose = () => setShowModal(false);

  const actions = (
    <button type="button" onClick={handleClose}>
      Close
    </button>
  );

  const modal = (
    <Modal onClose={handleClose} actions={actions}>
      <p>This is a modal!</p>
    </Modal>
  );

  return (
    <div className="container">
      <button type="button" onClick={handleClick}>
        Open
      </button>

      {showModal && modal}
    </div>
  );
};

export default App;Code language: JavaScript (javascript)

工作原理。

首先,将 onCloseactions 属性添加到 Modal 组件。

<Modal onClose={handleClose} actions={actions}>Code language: JavaScript (javascript)

其次,通过将子组件放置在开始和结束标签之间,将子组件提供给 Modal 组件

<Modal onClose={handleClose} actions={actions}>
    <p>This is a modal!</p>
</Modal>Code language: JavaScript (javascript)

请注意,您可以在 <Modal></Modal> 标签之间放置任何内容。在 Modal 组件中,它们将存储在 children 属性中。

第三,为模态框定义 actions,其中包括关闭按钮,并将 handleClose 事件处理程序注册到单击事件

const actions = (
    <button type="button" onClick={handleClose}>
      Close
    </button>
);Code language: JavaScript (javascript)

最后,在 handleClose 函数中将 showModal 状态设置为 false 以关闭模态框

const handleClose = () => setShowModal(false);Code language: JavaScript (javascript)

步骤 4. 将属性添加到 Modal 组件。

onClosechildrenactions 属性添加到 Modal 组件

import ReactDOM from 'react-dom';
import './Modal.css';

const Modal = ({ onClose, children, actions }) => {
  return ReactDOM.createPortal(
    <div className="modal-container" onClick={onClose}>
      <div className="modal">
        {children}
        {actions}
      </div>
    </div>,
    document.querySelector('.modal-wrapper')
  );
};

export default Modal;Code language: JavaScript (javascript)

Modal 组件中

  • onClose 函数注册到 .modal-container 元素的单击事件,以便在单击叠加层时关闭模态框。
  • 渲染 children 和 actions 属性。

如果您单击 打开 按钮,模态框将显示。当您单击叠加层或 关闭 按钮时,模态框将关闭。

但是,当您单击模态框内部时,它也会关闭。这是意外行为。原因是单击事件从 .modal-container 的子元素传播到 .modal-container 元素。

要阻止事件从子元素传播到父元素,可以使用事件对象的 stopPropagation() 方法。

import ReactDOM from 'react-dom';
import './Modal.css';

const Modal = ({ onClose, children, actions }) => {

  const handleClick = (e) => e.stopPropagation();

  return ReactDOM.createPortal(
    <div className="modal-container" onClick={onClose}>
      <div className="modal" onClick={handleClick}>
        {children}
        {actions}
      </div>
    </div>,
    document.querySelector('.modal-wrapper')
  );
};

export default Modal;Code language: JavaScript (javascript)

工作原理。

首先,将 onClick 属性添加到 .modal 元素

<div className="modal" onClick={handleClick}>Code language: JavaScript (javascript)

其次,在 handeClick 函数中调用事件对象的 stopPropagation() 方法来停止事件传播

const handleClick = (e) => e.stopPropagation();Code language: JavaScript (javascript)

现在,如果您单击模态框内部(除了 关闭 按钮),模态框将不会关闭。

修复滚动问题

如果 App 组件有很长的内容,并且您滚动它,叠加层将不会覆盖整个屏幕。

React Portals - Scroll

要解决此问题,您可以将 .modal-containerpositionabsolute 更改为 fixed

.modal-container {
  /* create an overlay */
  position: absolute;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.5);

  /* center the modal */
  display: flex;
  justify-content: center;
  align-items: center;
}Code language: CSS (css)

下载 React 模态框项目源代码

下载 React 模态框项目源代码

总结

  • 使用 React 传送门将子元素渲染到父组件 DOM 层级结构之外的 DOM 节点中。
本教程是否有帮助?