你真的了解React Portals吗

云中歌 2020-11-09 11:30:26
react 真的 了解 portals


在以往编写全局遮罩的时候,我通常是使用fixed定位,示例代码如下:

.modal {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index:500;
background: black;
}

这样来实现全局遮罩层,modal里边的内容自己控制。但是这样会存在一个问题,具体如下:

可以看到这个弹窗的内容并不是在body下面的一层,而是在层层嵌套的dom里边,这样会存在两个弊端:

  1. DOM结构不够优雅
  2. 父组件的样式可能会影响子组件的样式

这里提及一个真实案例,同事也是用的上面fixed定位的方式生成全局弹窗,但是发现按钮点击无法更改弹窗的显示,然后发现写了一个巨奇葩的代码,大概代码如下:

<!-- 这里是子组件 -->
return (
<button>
<span>toggle visible</span>
<modal visible={visible}></modal>
</button>
)

然后就发现狂点这个按钮不能控制modal的显示隐藏,因为这个button严重影响了modal的行为。
这种其实是很难排查问题的,你不知道用户写了什么奇葩的代码,这个时候就考虑其他方式来规避这个问题。在用户使用modal的时候,这个modal的dom实际是挂载在当前组件下,试想如果把这个DOM直接插入body下是不是可以规避这些问题
所幸的是React提供了这样的能力,下面介绍一下react提供的在子节点渲染到父组件以外的DOM节点的Portal

Portal介绍

Portal能够将子组件渲染到父组件以外的DOM树,他通常用于需要子组件需要从父组件的容器中脱离出来的场景,有以下场景:

  • Dialog 对话框
  • Tooltip 文字提示
  • Popover 弹出框
  • Loader 全局loader

下面是他的使用方法:

React.createPortal();

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
下面是使用react.createPortal的简单实例:

const Modal = ({message, visible, onClose, children}) => {
if (!visible) return null;
return ReactDOM.createPortal(
<div class="modal">
<span className="message">{message}</span>
<button onClick={onClose}>Close</button>
</div>
, domNode)
}

虽然portal脱离了父组件的容器限制,但是他的表现和正常的React组件一致。同样能够接收props和context。这是因为portal仍然存在react的层级树中。

为什么需要Portal

就如文章开头提到的一个案例,在某个组件中我们需要使用modal弹窗,大多数情况下我们可以使用fixed定位让这个弹窗全局展示,但是特殊情况下, 这个modal弹窗可能会显示不正常。所以这个时候如果我们使用了portal的方式直接modal的dom结构脱离父组件的容器,就可以规避这种问题。

const Modal = ({message, isOpen, onClose, children}) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal">
<span>{message}</span>
<button onClick={onClose}>Close</button>
</div>
, document.body)
}
function Component() {
const [open, setOpen] = useState(false)
return (
<div className="component">
<button onClick={() => setOpen(true)}></button>
<Modal
message="Hello World!"
isOpen={open}
onClose={() => setOpen(false)}
/>
</div>
)
}

上面这段代码就能够保证,无论子组件嵌套多深,这个modal能够和root同一级。使用chrome检查dom结构,就可以看到下面结构

使用Portal的注意点

在使用Portal的时候,需要知道就是虽然Portal可以被放置到DOM树的任何地方,但是行为和普通的React组件行为一致。由于 portal 仍存在于 React 树, 且与 DOM 树 中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性都是不变的。
下面是总结的一些注意事项。

  1. 事件冒泡: 一个从 portal 内部触发的事件会一直冒泡至包含 React 树的祖先,即便这些元素并不是 DOM 树 中的祖先。
  2. 生命周期: 即使是通过Portal创建的元素,这个元素仍然具有他的生命周期如componentDidMount等等
  3. 影响范围: Portal只会影响HTML的结构不会影响React树结构
  4. 挂载节点: 当使用Portal的时候必须定义一个真实的DOM节点作为Portal组件的挂载入口

总结

当我们需要在正常的DOM结构之外呈现子组件时,React Portal非常有用,而不需要通过React组件树层次结构破坏事件传播的默认行为,这在在渲染例如弹窗、提示非常有用

欢迎关注「前端好好学」,前端学习不迷路或加微信 ssdwbobo,一起交流学习
版权声明
本文为[云中歌]所创,转载请带上原文链接,感谢
https://segmentfault.com/a/1190000037755863

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple