【发布时间】:2019-02-05 18:15:32
【问题描述】:
背景:
我试图找出实现包装 React 的本机门户实用程序的门户组件的最佳方法。该组件将简单地处理创建门户的根元素,将其安全地插入 DOM,将组件的任何子元素渲染到其中,然后在卸载组件时再次从 DOM 中安全地删除它。
问题:
React 强烈建议不要在 React 的安全生命周期方法(componentDidMount、componentDidUpdate 等)之外产生副作用(如操作 DOM),因为这可能会导致问题(内存泄漏、陈旧节点等)。在 React 关于如何使用 Portal 的示例中,他们将 Portal 的根元素挂载到 componentDidMount 上的 DOM 树中,但这似乎会导致其他问题。
问题编号 1:
如果 Portal 组件 'portals' 在它的渲染方法期间它是创建的根元素的子元素,但在将根元素附加到 DOM 树之前等待它的 componentDidMount 方法触发,那么任何需要在其期间访问 DOM 的门户的子元素自己的 componentDidMount 生命周期方法会有问题,因为那时它们将被挂载到一个分离的节点。
这个issue 后来在 React 的文档中得到解决,该文档建议在 Portal 组件完成安装并成功将门户根元素附加到 DOM 树后,在 Portal 组件的状态上将“mounted”属性设置为 true。然后在渲染中,您可以推迟渲染 Portal 的任何子级,直到该 mount 属性设置为 true,因为这将保证所有这些子级将在其各自的 componentDidMount 生命周期方法之前渲染到实际的 DOM 树中会开火。伟大的。但这导致我们...
问题编号 2:
如果您的 Portal 组件推迟渲染它的任何子组件,直到它本身已挂载,则其祖先的任何 componentDidMount 生命周期方法也将在任何这些子组件被挂载之前触发。因此,任何 Portal 组件的祖先在他们自己的 componentDidMount 生命周期方法中需要访问任何这些子项的 refs 都会有问题。我还没有找到解决这个问题的好方法。
问题:
是否有一种干净的方法可以安全地实现门户组件,以便其子组件在其 componentDidMount 生命周期方法期间可以访问 DOM,同时还允许门户组件的祖先在其各自的 componentDidMount 生命周期中访问这些子组件的引用方法?
参考代码:
import { Component } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
export default class Portal extends Component {
static propTypes = {
/** This component uses Portals to dynamically render it's contents into
* whatever DOM Node contains the **id** supplied by this prop
* ('portal-root' by default). If a DOM Node cannot be found with the
* specified **id** then this component will create one and append it
* to the 'Document.Body'. */
rootId: PropTypes.string
};
static defaultProps = {
rootId: 'portal-root'
};
constructor(props) {
super(props);
this.state = { mounted: false };
this.portal = document.createElement('div');
}
componentDidMount() {
this.setRoot();
this.setState({ mounted: true });
}
componentDidUpdate( prevProps, prevState ) {
if( this.props.rootId !== prevProps.rootId ) this.setRoot();
}
componentWillUnmount() {
if( this.root ) {
this.root.removeChild(this.portal);
if( !this.root.hasChildNodes() ) this.root.parentNode.removeChild(this.root);
}
}
render() {
this.portal.className = this.props.className ? `${this.props.className} Portal` : 'Portal';
return this.state.mounted && ReactDOM.createPortal(
this.props.children,
this.portal,
);
}
setRoot = () => {
this.prevRoot = this.root;
this.root = document.getElementById(this.props.rootId);
if(!this.root) {
this.root = document.createElement('main');
this.root.id = this.props.rootId;
document.body.appendChild(this.root);
}
this.root.appendChild(this.portal);
if( this.prevRoot && !this.prevRoot.hasChildNodes() ) {
this.prevRoot.parentNode.removeChild(this.prevRoot);
}
}
}
【问题讨论】:
标签: javascript reactjs