“开启掘金成长之旅!这是我参与「掘金日新计划 · 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>
}
- 我们在上述代码中使用createPortal把元素
<h1>标题</h1>插入到id为title的元素中了
2. 注: 如果以上的js代码和ReactNode一起创建会引发异常,因为这时候html代码是还未初始化好的
- 修改以上代码
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>
}
- 实际开发中也不会有使用createPortal创建一个DOM 再同时插入旁边兄弟DOM中的做法
- 我们通过添加一个状态,再通过修改状态数据就可以解决上述问题,并达到我们的开发要求
3. 以上我们就完成了一个简单将子节点渲染到存在于父组件以外的 DOM 节点的方案
- 在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>
- 模拟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)
}
</>
}
- 使用自定义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>
}
3. 总结
- 通过学习Portals 并把元素挂载在非父节点上
- 自定义组件 通过使用React的Portals的方法 实现类似Vue的Teleport组件功能