React如何使用Portal实现跨层级DOM渲染

什么是 React Portal

在 React 里,组件渲染的元素默认会在组件的 DOM 层级里出现。不过有时候,我们可能希望把元素渲染到当前组件层级之外的地方,这时候 React Portal 就能派上用场啦。Portal 就像是一个“传送门”,能让你把组件里的元素“传送到”其他 DOM 节点下面去渲染,即便这些 DOM 节点不在当前组件的 DOM 层级里。

为什么需要 Portal

通常在处理一些特殊的 UI 场景时,默认的渲染方式可能没办法满足需求。比如,模态框、下拉菜单、提示框这类需要在整个页面层级上显示的元素,用 Portal 就能很方便地实现跨层级渲染,避免一些样式和布局上的问题。

如何使用 Portal

下面是一个简单的例子,我们来创建一个模态框组件,用 Portal 把它渲染到 body 元素下面。

import React from 'react';
import ReactDOM from 'react-dom';

// 创建一个模态框组件
const Modal = ({ isOpen, onClose, children }) => {
    // 如果模态框没打开,就不渲染任何内容
    if (!isOpen) {
        return null;
    }

    // 使用 createPortal 方法把模态框内容渲染到指定的 DOM 节点
    return ReactDOM.createPortal(
        // 模态框的外层容器,添加了一些样式和事件处理
        <div className="modal-overlay" onClick={onClose}>
            <div className="modal-content" onClick={(e) => e.stopPropagation()}>
                {/* 这里是模态框的内容 */}
                {children}
                {/* 关闭模态框的按钮 */}
                <button onClick={onClose}>关闭</button>
            </div>
        </div>,
        // 指定要渲染到的 DOM 节点,这里是 body 元素
        document.body
    );
};

// 创建一个父组件来使用模态框
const App = () => {
    // 使用 useState 钩子来管理模态框的打开和关闭状态
    const [isModalOpen, setIsModalOpen] = React.useState(false);

    // 打开模态框的函数
    const openModal = () => {
        setIsModalOpen(true);
    };

    // 关闭模态框的函数
    const closeModal = () => {
        setIsModalOpen(false);
    };

    return (
        <div>
            {/* 打开模态框的按钮 */}
            <button onClick={openModal}>打开模态框</button>
            {/* 使用模态框组件 */}
            <Modal isOpen={isModalOpen} onClose={closeModal}>
                <h2>这是一个模态框</h2>
                <p>这里可以放模态框的具体内容。</p>
            </Modal>
        </div>
    );
};

export default App;

代码解释

1.Modal 组件:

  • 它接收三个属性:isOpen 用来判断模态框是否打开,onClose 是关闭模态框的回调函数,children 是模态框里要显示的内容。
  • 当 isOpen 为 false 时,组件不渲染任何内容。
  • 用 ReactDOM.createPortal 方法把模态框的内容渲染到 document.body 下面。
  • 模态框的外层容器有一个点击事件,点击时会调用 onClose 函数关闭模态框。
  • 模态框的内容部分也有一个点击事件,用 e.stopPropagation() 阻止事件冒泡,避免点击内容时也关闭模态框。

2.App 组件:

  • 用 useState 钩子来管理模态框的打开和关闭状态。
  • 有一个“打开模态框”的按钮,点击时调用 openModal 函数把 isModalOpen 设为 true。
  • 使用 Modal 组件,并传入相应的属性。

适用场景

模态框:像上面的例子一样,模态框通常需要覆盖整个页面,用 Portal 渲染到 body 下面能避免样式受父组件的影响。

下拉菜单:下拉菜单可能需要在页面的任何位置显示,用 Portal 可以让它独立于父组件的布局。

提示框:提示框也需要在页面的合适位置显示,使用 Portal 能更好地控制其显示位置和样式。

通过使用 React Portal,你可以更灵活地控制组件的渲染位置,解决一些跨层级渲染的问题。

React Portal优势

React Portal 是 React 提供的一种强大特性,和传统的 DOM 操作方式相比,它具有下面这些显著优势:

1. 保持 React 组件的声明式特性

在 React 里,开发基于声明式的编程范式,也就是通过描述 UI 最终呈现的样子,让 React 来处理具体的渲染逻辑。传统的 DOM 操作通常是命令式的,需要手动操作 DOM 元素,像创建、插入、删除节点等。而 React Portal 可以让你在保持声明式风格的同时,把组件渲染到任意的 DOM 节点下。

例如,使用 React Portal 创建模态框时,你只需定义好模态框的组件结构,然后通过 ReactDOM.createPortal 把它渲染到指定位置,而不需要手动操作 DOM 来创建和显示模态框。

import React from 'react';
import ReactDOM from 'react-dom';

const Modal = ({ isOpen, onClose, children }) => {
    if (!isOpen) {
        return null;
    }
    return ReactDOM.createPortal(
        <div className="modal-overlay" onClick={onClose}>
            <div className="modal-content" onClick={(e) => e.stopPropagation()}>
                {children}
                <button onClick={onClose}>关闭</button>
            </div>
        </div>,
        document.body
    );
};

2. 组件的逻辑与渲染分离

使用 React Portal 能够让组件的逻辑和渲染位置解耦。组件内部的逻辑可以保持独立,不受渲染位置的影响,这样可以提高组件的可复用性和可维护性。

比如,一个下拉菜单组件,不管它是在页面的顶部、侧边还是其他位置渲染,组件内部的逻辑(如菜单项的显示、点击事件处理等)都不需要改变。

import React from 'react';
import ReactDOM from 'react-dom';

​​​​​​​const Dropdown = ({ isOpen, items }) => {
    if (!isOpen) {
        return null;
    }
    return ReactDOM.createPortal(
        <div className="dropdown">
            {items.map((item, index) => (
                <div key={index}>{item}</div>
            ))}
        </div>,
        document.body
    );
};

3. 更好地管理事件和状态

React Portal 依然遵循 React 的事件系统和状态管理机制。这意味着你可以使用 React 的事件处理函数和状态钩子(如 useState、useReducer)来管理组件的交互和状态,而不需要担心 DOM 操作带来的事件冒泡和状态同步问题。

例如,在上面的模态框例子中,点击关闭按钮时,直接调用 onClose 函数来更新状态,从而关闭模态框,React 会自动处理状态更新和 UI 重新渲染。

4. 避免样式和布局冲突

当把组件渲染到不同的 DOM 层级时,传统的 DOM 操作可能会导致样式和布局冲突。而 React Portal 可以让组件在合适的 DOM 节点下渲染,避免父组件的样式和布局对其产生影响。

比如,模态框通常需要覆盖整个页面,使用 React Portal 把模态框渲染到 body 下面,可以避免父组件的 overflow 样式影响模态框的显示。

5. 方便与 React 生态系统集成

React Portal 是 React 生态系统的一部分,可以和其他 React 特性(如上下文、高阶组件、钩子等)无缝集成。这让开发者能够更方便地构建复杂的应用程序。

例如,你可以在使用 React Portal 的组件中使用 useContext 钩子来共享状态,或者使用高阶组件来增强组件的功能。

综上所述,React Portal 提供了一种更简洁、高效、可维护的方式来处理跨层级的 DOM 渲染,避免了传统 DOM 操作带来的一些问题。

有哪些场景不适合使用React Portal

虽然 React Portal 为跨层级 DOM 渲染提供了便利,但在某些实际开发场景中,使用它可能并不是最佳选择,以下是一些不适合使用 React Portal 的场景:

1. 简单的组件嵌套场景

当组件的嵌套结构简单,并且不需要跨层级渲染时,使用 React Portal 会增加代码的复杂性。例如,一个简单的表单组件,其所有子组件都可以自然地嵌套在表单组件内部,没有必要使用 Portal 来渲染这些子组件。

import React from 'react';

const SimpleForm = () => {
    return (
        <form>
            <label>姓名: </label>
            <input type="text" />
            <button type="submit">提交</button>
        </form>
    );
};

export default SimpleForm;

在这个例子中,表单组件及其子元素可以正常嵌套渲染,使用 Portal 只会让代码变得复杂,没有实际的好处。

2. 对性能要求极高的场景

使用 React Portal 会增加一定的性能开销,因为它需要额外的逻辑来处理跨层级的渲染。在对性能要求极高的场景中,如高频更新的游戏界面或者实时数据展示界面,频繁使用 Portal 可能会导致性能下降。

例如,一个实时股票价格显示组件,每秒需要更新多次数据,如果使用 Portal 来渲染价格显示区域,可能会因为额外的渲染逻辑导致界面卡顿。

3. 组件的渲染依赖于父组件布局的场景

如果组件的渲染和布局高度依赖于其父组件的样式和布局,使用 React Portal 将组件渲染到其他 DOM 节点可能会破坏这种依赖关系。

比如,一个卡片组件,其内部的子元素需要根据卡片的宽度和高度进行自适应布局。如果使用 Portal 将子元素渲染到其他地方,就无法利用父组件的布局信息,导致布局错乱。

import React from 'react';

​​​​​​​const Card = () => {
    return (
        <div className="card">
            {/* 子元素依赖于卡片的布局 */}
            <div className="card-content">
                <p>这是卡片内容</p>
            </div>
        </div>
    );
};

在这种情况下,将 card-content 用 Portal 渲染到其他地方,就会破坏其与 card 的布局依赖关系。

4. 代码维护和调试困难的场景

当项目规模较大、组件关系复杂时,过度使用 React Portal 会使代码的维护和调试变得困难。因为 Portal 打破了常规的组件嵌套结构,开发人员在查找和理解组件之间的关系时会花费更多的精力。

例如,在一个大型的企业级应用中,如果有大量的组件使用 Portal 进行跨层级渲染,当出现问题时,很难快速定位到问题所在的组件和渲染位置。

5. 兼容性要求严格的场景

虽然 React Portal 是一个强大的特性,但在一些旧版本的浏览器或者特定的环境中,可能存在兼容性问题。如果项目对浏览器兼容性有严格的要求,使用 Portal 可能会带来不必要的麻烦。

例如,一些老旧的浏览器可能不支持 React 所依赖的某些特性,导致 Portal 无法正常工作。在这种情况下,需要考虑其他替代方案来实现跨层级渲染。

React Portal的工作原理

React Portal 是 React 提供的一个强大特性,它允许你将子组件渲染到父组件 DOM 层级之外的 DOM 节点中,下面详细解释其工作原理。

1. 核心概念

在 React 中,组件默认的渲染方式是将其生成的元素插入到其父组件对应的 DOM 节点内部,形成嵌套的 DOM 结构。而 React Portal 打破了这种默认的渲染规则,让组件可以将其内容渲染到指定的任意 DOM 节点下,即便这个节点不在当前组件的正常 DOM 层级里。

2. 关键 API:ReactDOM.createPortal

ReactDOM.createPortal 是实现 React Portal 的核心方法,它的函数签名如下:

ReactDOM.createPortal(child, container);
  • child:这是要渲染的 React 元素、组件或者片段等,也就是你想要通过 Portal 渲染的内容。
  • container:这是一个 DOM 节点,它指定了 child 最终要被渲染到的目标位置。

3. 工作流程

下面结合一个简单的示例来详细说明 React Portal 的工作流程。

import React from 'react';
import ReactDOM from 'react-dom';

// 创建一个 Portal 组件
const PortalComponent = () => {
    return ReactDOM.createPortal(
        <div className="portal-content">
            这是通过 Portal 渲染的内容
        </div>,
        document.getElementById('portal-root')
    );
};

// 父组件
const ParentComponent = () => {
    return (
        <div className="parent">
            <h1>父组件</h1>
            <PortalComponent />
        </div>
    );
};

// 假设在 HTML 文件中有一个 id 为 portal-root 的元素
// <div id="portal-root"></div>

​​​​​​​export default ParentComponent;

步骤 1:渲染父组件

当 React 渲染 ParentComponent 时,它会按照常规的渲染流程处理组件内的子元素。在 ParentComponent 中遇到 PortalComponent 时,会调用 PortalComponent 的渲染逻辑。

步骤 2:调用 createPortal 方法

在 PortalComponent 中,调用 ReactDOM.createPortal 方法。这个方法接收两个参数:要渲染的内容(这里是一个包含文本的 div 元素)和目标 DOM 节点(通过 document.getElementById(‘portal-root’) 获取)。

步骤 3:查找目标 DOM 节点

createPortal 方法会在浏览器的 DOM 树中查找指定的目标 DOM 节点(这里是 id 为 portal-root 的元素)。如果找到了目标节点,就会继续下一步;如果没找到,可能会抛出错误或者不进行渲染。

步骤 4:渲染内容到目标节点

一旦找到目标 DOM 节点,createPortal 方法会将 child 元素渲染到这个目标节点内部。此时,虽然 PortalComponent 是 ParentComponent 的子组件,但它渲染的内容不会出现在 ParentComponent 对应的 DOM 节点内部,而是出现在 portal-root 节点下。

4. 事件冒泡和 React 树的关系

尽管 Portal 的内容渲染在 DOM 树的其他位置,但在 React 组件树中,它仍然被视为其父组件的子元素。这意味着事件冒泡会按照 React 组件树的结构进行,而不是按照实际的 DOM 树结构。

例如,在上面的示例中,如果在 portal-content 元素上触发了一个点击事件,这个事件会冒泡到 PortalComponent 及其父组件 ParentComponent,就好像 portal-content 元素是直接嵌套在 ParentComponent 中的一样。

总结

React Portal 通过 ReactDOM.createPortal 方法打破了组件渲染受限于父组件 DOM 层级的规则,允许将组件内容渲染到任意 DOM 节点。同时,它保持了 React 组件树的逻辑结构,使得事件处理等操作仍然遵循 React 的规则。这种特性在处理模态框、下拉菜单等需要跨层级渲染的场景中非常有用。

到此这篇关于React如何使用Portal实现跨层级DOM渲染的文章就介绍到这了,更多相关React Portal内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源链接:https://www.jb51.net/javascript/33894856p.htm

© 版权声明
THE END
支持一下吧
点赞15 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容