Skip to content

Portals

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

    ReactDOM.createPortal(child,container)

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

用法

通常来讲,当你从组件的render方法返回元素的时,该元素将被挂在到DOM节点中里其最近的父节点。

render(){
    return (
        <div>
        {this.props.children}
        </div>
    )
}

然而,有时候将子元素插入到DOM节点中的不同位置也是有好处的:

render(){
    return  ReactDOM.createPortal(
        this.props.children,
        domNode
    )
}

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

通过Portal进行事件冒泡

尽管 portal 可以被防止在 DOM 树中的任何地方,但在任何其他方面,其行为和普通的 React 子节点行为一致。由于 portal 仍存在于 React 树,且与 DOM 树中的位置无关,那么无论其子节点是否是 portal,像 context 这样的功能特性是不变的。 这包括事件冒泡。一个从 portal 内部触发的事件会一直冒泡至 React 树的组件。即使这些元素并不是 DOM 树中的祖先。

<html>
  <body>
    <div id="app-root"></div>
    <div id="modal-root"></div>
  </body>
</html>

#app-root里面Parent组件能捕获到未被捕获的从兄弟节点#modal-root冒泡上来的事件。

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);
    }
    componentDidUnMount () {
        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>
                <Modal>
                    <Child />
                </Modal>
            </div>
        )
    }
}


function Child(){
    return (
        <div className = "modal">
            <button>clcik</button>
        </div>
    )
}

ReactDOM.render(<Parent  /> , appRoot)

在父组件里捕获一个来自portal冒泡上来得事件,使之能够在开发时具有不完全依赖portal得更灵活得抽象。例如,如果你在渲染一个<Modal />组件,无论其是否采用portal实现,父组件都能够捕获其事件。