Skip to content

Portals ​

本节目标:理解“Portals”的核心思路,并能把它用于实际开发或面试表达。

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。

ReactDOM.createPortal(child, container) 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。

**用法**
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:
render() {
    // React 挂载了一个新的 div,并且把子元素渲染其中
    return (
        <div>
            {this.props.children}
        </div>
    );
}
然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:
render() {
    // React 并*没有*创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
    // `domNode` 是一个可以在任何位置的有效 DOM 节点。
    return ReactDOM.createPortal(
        this.props.children,
        domNode
    );
}

典型应用 一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:

注意**😗* 当在使用 portal 时, 记住管理键盘焦点就变得尤为重要。 对于模态对话框,通过遵循 WAI-ARIA 模态开发实践,来确保每个人都能够运用它。

**通过** **Portal** **进行事件冒泡**
尽管 portal 可以被放置在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 仍存在于 _React_ _树_, 且与 _DOM_ _树_ 中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性都是不变的。
这包含事件冒泡。一个从 portal 内部触发的事件会一直冒泡至包含 _React_ _树_的祖先,即便这些元素并不是 _DOM_ _树_ 中的祖先。假设存在如下 HTML 结构:
<html>  <body>    <div id="app-root"></div>    <div id="modal-root"></div>  </body></html>
在 #app-root 里的 Parent 组件能够捕获到未被捕获的从兄弟节点 #modal-root 冒泡上来的事件。
// 在 DOM 中有两个容器是兄弟级 (siblings)
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
    constructor(props) {
        super(props);
        this.el = document.createElement('div');
    }
    componentDidMount() {
        // 在 Modal 的所有子元素被挂载后,
        // 这个 portal 元素会被嵌入到 DOM 树中,
        // 这意味着子元素将被挂载到一个分离的 DOM 节点中。
        // 如果要求子组件在挂载时可以立刻接入 DOM 树,
        // 例如衡量一个 DOM 节点,
        // 或者在后代节点中使用 ‘autoFocus’,
        // 则需添加 state 到 Modal 中,
        // 仅当 Modal 被插入 DOM 树中才能渲染子元素。
        modalRoot.appendChild(this.el);
    }
    componentWillUnmount() {
        modalRoot.removeChild(this.el);
    }
    render() {
        return ReactDOM.createPortal(this.props.children, this.el);
    }
}
class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.state = { clicks: 0 };
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick() {
        // 当子元素里的按钮被点击时,
        // 这个将会被触发更新父元素的 state,
        // 即使这个按钮在 DOM 中不是直接关联的后代
        this.setState(state => ({ clicks: state.clicks + 1 }));
    }
    render() {
        return (
            <div onClick={this.handleClick}>
                <p>Number of clicks: {this.state.clicks}</p>
                <p>
                    Open up the browser DevTools
                    to observe that the button
                    is not a child of the div
                    with the onClick handler.
                </p>
                <Modal>
                    <Child />
                </Modal>
            </div>
        );
    }
}
function Child() {
    // 这个按钮的点击事件会冒泡到父元素
    // 因为这里没有定义 'onClick' 属性
    return (
        <div className="modal">
            <button>Click</button>
        </div>
    );
}
ReactDOM.render(<Parent />, appRoot);
在父组件里捕获一个来自 portal 冒泡上来的事件,使之能够在开发时具有不完全依赖于 portal 的更为灵活的抽象。例如,如果你在渲染一个 <Modal /> 组件,无论其是否采用 portal 实现,父组件都能够捕获其事件。
v16之前传送出去的组件不冒泡回来了(应该是在传送出去的那一端冒泡 有待考证) v16之后传送出去的组件事件会冒泡回来

> 来自

 <https://react.docschina.org/docs/portals.html>

本站总访问 — 次