如何在首次渲染时将React Portal挂载到当前组件节点?
如何在首次渲染时将React Portal挂载到组件内节点
嘿,我完全懂你的感受——靠componentDidMount里设状态触发重渲染确实有点别扭,咱们来把这个逻辑优化得更优雅些。
问题根源
你现在遇到的问题,本质是React ref的赋值时机:在组件首次render的时候,this.productForm.current还是null,因为React要等DOM挂载完成后才会把ref指向对应的节点。所以你不得不等组件挂载完,用mounted状态触发第二次渲染,才能拿到有效的DOM节点传给Portal。
优化方案:让Portal组件适配ref对象
最根本的解决办法是调整你的Portal组件,让它可以直接接收ref对象,而不是要求必须传入已存在的DOM节点。这样Portal可以在自己的生命周期里去获取ref对应的DOM节点,不用父组件额外触发重渲染。
假设你的Portal组件原本是这样的(简化版):
import React from 'react'; import ReactDOM from 'react-dom'; class Portal extends React.Component { constructor(props) { super(props); this.el = document.createElement('div'); } componentDidMount() { // 原本直接接收DOM节点 this.props.node.appendChild(this.el); } componentWillUnmount() { this.props.node.removeChild(this.el); } render() { return ReactDOM.createPortal(this.props.children, this.el); } }
我们把它改成支持接收ref对象:
import React from 'react'; import ReactDOM from 'react-dom'; class Portal extends React.Component { constructor(props) { super(props); this.el = document.createElement('div'); } componentDidMount() { // 判断传入的是ref对象还是直接的DOM节点 const targetNode = this.props.node.current || this.props.node; targetNode.appendChild(this.el); } componentWillUnmount() { const targetNode = this.props.node.current || this.props.node; targetNode.removeChild(this.el); } render() { return ReactDOM.createPortal(this.props.children, this.el); } }
父组件简化写法
现在你的Users组件就完全不需要mounted状态了,直接把ref对象传给Portal即可:
import React from 'react'; import PropTypes from 'prop-types'; import FormProduct from 'containers/FormProduct'; import Portal from 'shared/Portal'; class Users extends React.Component { constructor(props) { super(props); this.productForm = React.createRef(); } render() { return ( <React.Fragment> <div className="mt-20" ref={this.productForm}></div> <Portal node={this.productForm}> <FormProduct /> </Portal> </React.Fragment> ) } }
为什么这样可行?
React的生命周期执行顺序是:
- 父组件
render,生成虚拟DOM,此时ref还未指向真实DOM - 子组件(Portal)
render - 真实DOM挂载完成,父组件的ref被赋值为对应的DOM节点
- 子组件(Portal)执行
componentDidMount,此时可以通过this.props.node.current拿到有效的DOM节点,完成Portal挂载
整个过程只需要一次渲染,完全不需要额外的状态触发重渲染。
如果你不能修改Portal组件?
如果出于某些原因无法修改Portal组件,还有一个替代方案:在父组件的componentDidMount里直接手动创建Portal的内容,但这种方式会失去React声明式渲染的优势,不太推荐。示例如下:
componentDidMount() { ReactDOM.createPortal( <FormProduct />, this.productForm.current ); }
但还是建议优先调整Portal组件的实现,这样代码会更简洁、符合React的设计理念。
内容的提问来源于stack exchange,提问作者Manish Jangir
相关产品推荐
相关产品推荐

