学习React 的第十五天 函数式组件使用Portals

328 阅读2分钟

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15 天,点击查看活动详情

1. 简介

在实际开发中我们可能会遇到这样的场景,一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。

简单来说就是我需要一个弹框功能,点击按钮就弹出弹框,但是弹框需要渲染在项目的#app节点或者body节点

在React 中需要实现这个功能 我们需要使用到React高级指引的Portals功能

2. 代码案例

1. 在函数式组件中,通过方法createPortal 渲染到指定的元素中

function App() {
    return <AppDiv id="app">
        <div>
            这里需要标题
            <div id="title"></div>
        </div>
        {
            createPortal(<h1>标题</h1>, document.getElementById("title") as HTMLElement)
        }
    </AppDiv>
}

截图_16766215512205.png

  1. 我们在上述代码中使用createPortal把元素<h1>标题</h1>插入到id为title的元素中了

2. 注: 如果以上的js代码和ReactNode一起创建会引发异常,因为这时候html代码是还未初始化好的

  1. 修改以上代码
function App() {
    const [modelEl, openModalEl] = useState(false);


    return <AppDiv id="app">
        <div>
            这里需要标题
            <div id="title"></div>
        </div>
        {
            modelEl && createPortal(<h1>标题</h1>, document.getElementById("title") as HTMLElement)
        }

        <button onClick={() => openModalEl(true)}>点击</button>
    </AppDiv>
}
  1. 实际开发中也不会有使用createPortal创建一个DOM 再同时插入旁边兄弟DOM中的做法
  2. 我们通过添加一个状态,再通过修改状态数据就可以解决上述问题,并达到我们的开发要求 截图_1676621902228.png

3. 以上我们就完成了一个简单将子节点渲染到存在于父组件以外的 DOM 节点的方案

  1. 在Vue中也有一个类似的功能,叫做 Teleport内置组件
<button @click="open = true">Open Modal</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>
  1. 模拟Vue的Teleport 开发一个类似组件

轻松一步,使用默认插槽直接定义一个组件

import { ReactNode } from "react";
import { createPortal } from "react-dom";

interface propsType {
    to: string
    children?: ReactNode
}

export default function Teleport({ to, children }: propsType) {

    return <>
        {
            document.getElementById(to) && createPortal(children, document.getElementById(to) as HTMLElement)
        }
    </>
}
  1. 使用自定义Teleport组件

可以看到元素Modal 被像一个弹框一样被挂载到了id为root的DOM上

function App() {
    const [modelEl, openModalEl] = useState(false);


    return <AppDiv id="app">

        <button onClick={() => openModalEl(!modelEl)}>{modelEl ? '关闭' : '打开'}</button>


        <Teleport to="root">
            {modelEl && <Modal>弹窗</Modal>}
        </Teleport>

    </AppDiv>
}

截图_1676622486471.png

截图_16766224916570.png

3. 总结

  1. 通过学习Portals 并把元素挂载在非父节点上
  2. 自定义组件 通过使用React的Portals的方法 实现类似Vue的Teleport组件功能