简介

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。

Even though a portal can be anywhere in the DOM tree, it behaves like a normal React child in every other way. Features like context work exactly the same regardless of whether the child is a portal, as the portal still exists in the React tree regardless of position in the DOM tree.

尽管 portal 可以被放置在 DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致。无论其子节点是否是 portal,上下文特性依然能够如之前一样正确地工作。由于 portal 仍存在于 React 树中,而不用考虑其在 DOM 树中的位置。

场景

在编写组件的时候,有时需要一些对话框,但添加对话框的时候,需要将组件放到最外层,那样做的场景中需要将更改状态的方法传入到组件,在组件中控制最外层的渲染。这样就会导致整个组件的更新。

portals可以很好的解决此问题。而不必使用管理状态的工具,或者其他的操作。

  1. ReactDOM.createPortal(child, container)

The first argument (child) is any renderable React child, such as an element, string, or fragment. The second argument (container) is a DOM element.

使用方法

通常,编写的组件会返回一个element、fragment,这个组件会挂载在父级的组件。

  1. render() {
  2. // React mounts a new div and renders the children into it
  3. return (
  4. <div>
  5. {this.props.children}
  6. </div>
  7. );
  8. }

但,例如这个例子,我们想将组件挂载到指定的地方?

  1. render() {
  2. // React does *not* create a new div. It renders the children into `domNode`.
  3. // `domNode` is any valid DOM node, regardless of its location in the DOM.
  4. return ReactDOM.createPortal(
  5. this.props.children,
  6. domNode
  7. );
  8. }

例如

  1. <div id="app-root"></div>
  2. <div id="modal-root"></div>
  1. // These two containers are siblings in the DOM
  2. const appRoot = document.getElementById('app-root');
  3. const modalRoot = document.getElementById('modal-root');
  4. // Let's create a Modal component that is an abstraction around
  5. // the portal API.
  6. class Modal extends React.Component {
  7. constructor(props) {
  8. super(props);
  9. // Create a div that we'll render the modal into. Because each
  10. // Modal component has its own element, we can render multiple
  11. // modal components into the modal container.
  12. this.el = document.createElement('div');
  13. }
  14. componentDidMount() {
  15. // Append the element into the DOM on mount. We'll render
  16. // into the modal container element (see the HTML tab).
  17. modalRoot.appendChild(this.el);
  18. }
  19. componentWillUnmount() {
  20. // Remove the element from the DOM when we unmount
  21. modalRoot.removeChild(this.el);
  22. }
  23. render() {
  24. // Use a portal to render the children into the element
  25. return ReactDOM.createPortal(
  26. // Any valid React child: JSX, strings, arrays, etc.
  27. this.props.children,
  28. // A DOM element
  29. this.el,
  30. );
  31. }
  32. }
  33. // The Modal component is a normal React component, so we can
  34. // render it wherever we like without needing to know that it's
  35. // implemented with portals.
  36. class App extends React.Component {
  37. constructor(props) {
  38. super(props);
  39. this.state = {showModal: false};
  40. this.handleShow = this.handleShow.bind(this);
  41. this.handleHide = this.handleHide.bind(this);
  42. }
  43. handleShow() {
  44. this.setState({showModal: true});
  45. }
  46. handleHide() {
  47. this.setState({showModal: false});
  48. }
  49. render() {
  50. // Show a Modal on click.
  51. // (In a real app, don't forget to use ARIA attributes
  52. // for accessibility!)
  53. const modal = this.state.showModal ? (
  54. <Modal>
  55. <div className="modal">
  56. <div>
  57. With a portal, we can render content into a different
  58. part of the DOM, as if it were any other React child.
  59. </div>
  60. This is being rendered inside the #modal-container div.
  61. <button onClick={this.handleHide}>Hide modal</button>
  62. </div>
  63. </Modal>
  64. ) : null;
  65. return (
  66. <div className="app">
  67. This div has overflow: hidden.
  68. <button onClick={this.handleShow}>Show modal</button>
  69. {modal}
  70. </div>
  71. );
  72. }
  73. }
  74. ReactDOM.render(<App />, appRoot);

事件捕获的例子

An event fired from inside a portal will propagate to ancestors in the containing React tree, even if those elements are not ancestors in the DOM tree.

  1. <div id="app-root"></div>
  2. <div id="modal-root"></div>
  1. // These two containers are siblings in the DOM
  2. const appRoot = document.getElementById('app-root');
  3. const modalRoot = document.getElementById('modal-root');
  4. class Modal extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.el = document.createElement('div');
  8. }
  9. componentDidMount() {
  10. // The portal element is inserted in the DOM tree after
  11. // the Modal's children are mounted, meaning that children
  12. // will be mounted on a detached DOM node. If a child
  13. // component requires to be attached to the DOM tree
  14. // immediately when mounted, for example to measure a
  15. // DOM node, or uses 'autoFocus' in a descendant, add
  16. // state to Modal and only render the children when Modal
  17. // is inserted in the DOM tree.
  18. modalRoot.appendChild(this.el);
  19. }
  20. componentWillUnmount() {
  21. modalRoot.removeChild(this.el);
  22. }
  23. render() {
  24. return ReactDOM.createPortal(
  25. this.props.children,
  26. this.el,
  27. );
  28. }
  29. }
  30. class Parent extends React.Component {
  31. constructor(props) {
  32. super(props);
  33. this.state = {clicks: 0};
  34. this.handleClick = this.handleClick.bind(this);
  35. }
  36. handleClick() {
  37. // This will fire when the button in Child is clicked,
  38. // updating Parent's state, even though button
  39. // is not direct descendant in the DOM.
  40. this.setState(state => ({
  41. clicks: state.clicks + 1
  42. }));
  43. }
  44. render() {
  45. return (
  46. <div onClick={this.handleClick}>
  47. <p>Number of clicks: {this.state.clicks}</p>
  48. <p>
  49. Open up the browser DevTools
  50. to observe that the button
  51. is not a child of the div
  52. with the onClick handler.
  53. </p>
  54. <Modal>
  55. <Child />
  56. </Modal>
  57. </div>
  58. );
  59. }
  60. }
  61. function Child() {
  62. // The click event on this button will bubble up to parent,
  63. // because there is no 'onClick' attribute defined
  64. return (
  65. <div className="modal">
  66. <button>Click</button>
  67. </div>
  68. );
  69. }
  70. ReactDOM.render(<Parent />, appRoot);

Catching an event bubbling up from a portal in a parent component allows the development of more flexible abstractions that are not inherently reliant on portals. For example, if you render a component, the parent can capture its events regardless of whether it’s implemented using portals.
在父组件里捕获一个来自 portal 的事件冒泡能够在开发时具有不完全依赖于 portal 的更为灵活的抽象。例如,若你在渲染一个 组件,父组件能够捕获其事件而无论其是否采用 portal 实现。

参考