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

React 16中ReactDOM.createPortal实现问题:Child如何在GrandParent中渲染并卸载Parent

解决React Portal实现Child渲染到GrandParent且Parent卸载的问题

我明白你现在的困惑——Portal看起来简单,但要结合组件卸载的需求,确实需要调整一下组件的逻辑结构。之前你尝试在Parent里直接用Portal,结果Child和Parent共存,是因为Parent本身并没有被卸载,只是Child的DOM位置变了而已。下面给你两种具体的实现方案,适配不同的场景:


方案一:结合路由(适合你的Route场景)

如果你的项目用了React Router,这是最贴合你现有代码的方式:

1. 先改造GrandParent组件

在GrandParent里准备好Portal的目标容器,同时把路由的顶层逻辑放在这里:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Parent from './Parent';
import Child from './Child';

class GrandParent extends Component {
  render() {
    // 拿到Portal要挂载的DOM节点
    const portalTarget = document.getElementById('grand-parent-portal');
    
    return (
      <Router>
        <div className="grand-parent">
          {/* 这是Child最终要渲染到的位置 */}
          <div id="grand-parent-portal"></div>
          
          {/* 路由配置:匹配/parent时渲染Parent,匹配/child时用Portal渲染Child */}
          <Switch>
            <Route path="/parent" component={Parent} />
            <Route 
              path="/child" 
              render={() => {
                // 确保目标节点存在再渲染
                return portalTarget ? ReactDOM.createPortal(<Child />, portalTarget) : null;
              }} 
            />
          </Switch>
        </div>
      </Router>
    );
  }
}

export default GrandParent;

2. 调整Parent组件

只需要保留跳转链接,路由切换后Parent会自动卸载:

import React, { Component } from 'react';
import { Link } from 'react-router-dom';

class Parent extends Component {
  componentDidMount() {
    // 你的原有逻辑保持不变
  }

  render() {
    return (
      <div className="parent">
        <p>我是Parent组件</p>
        {/* 点击后路由切换到/child,Parent会因为路由不匹配被卸载 */}
        <Link to="/child">点击跳转到Child</Link>
      </div>
    );
  }
}

export default Parent;

方案二:通过状态控制(无需路由的场景)

如果不用路由,用组件状态也能实现同样效果:

1. GrandParent组件(控制显示逻辑)

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Parent from './Parent';
import Child from './Child';

class GrandParent extends Component {
  state = {
    showChild: false
  };

  // 接收Parent传来的触发事件
  handleShowChild = () => {
    this.setState({ showChild: true });
  };

  render() {
    const { showChild } = this.state;
    const portalTarget = document.getElementById('grand-parent-portal');

    return (
      <div className="grand-parent">
        <div id="grand-parent-portal"></div>
        
        {/* showChild为false时渲染Parent,true时Parent自动卸载 */}
        {!showChild && <Parent onShowChild={this.handleShowChild} />}
        
        {/* showChild为true时,通过Portal把Child渲染到目标容器 */}
        {showChild && portalTarget && ReactDOM.createPortal(<Child />, portalTarget)}
      </div>
    );
  }
}

export default GrandParent;

2. Parent组件(触发状态变化)

import React, { Component } from 'react';

class Parent extends Component {
  render() {
    const { onShowChild } = this.props;
    return (
      <div className="parent">
        <p>我是Parent组件</p>
        <button onClick={onShowChild}>点击显示Child并卸载我</button>
      </div>
    );
  }
}

export default Parent;

关键注意点

  1. Portal只是改变DOM位置,不改变组件逻辑归属:你之前的问题是Parent没有被卸载,所以即使Child通过Portal移到了GrandParent里,Parent本身还在DOM中。必须让Parent的渲染条件失效(路由切换/状态变化),才能让它卸载。
  2. 确保Portal目标节点已存在:在调用ReactDOM.createPortal前,要确认目标DOM元素已经被渲染到页面中,不然会报错。

内容的提问来源于stack exchange,提问作者Constance G

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:53:54