You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在首次渲染时将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的生命周期执行顺序是:

  1. 父组件render,生成虚拟DOM,此时ref还未指向真实DOM
  2. 子组件(Portal)render
  3. 真实DOM挂载完成,父组件的ref被赋值为对应的DOM节点
  4. 子组件(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:32:24