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;
关键注意点
- Portal只是改变DOM位置,不改变组件逻辑归属:你之前的问题是Parent没有被卸载,所以即使Child通过Portal移到了GrandParent里,Parent本身还在DOM中。必须让Parent的渲染条件失效(路由切换/状态变化),才能让它卸载。
- 确保Portal目标节点已存在:在调用
ReactDOM.createPortal前,要确认目标DOM元素已经被渲染到页面中,不然会报错。
内容的提问来源于stack exchange,提问作者Constance G
相关产品推荐
相关产品推荐

