你不知道的React系列(九)Portals

109 阅读1分钟

「回顾 2022,展望 2023,我正在参与2022 年终总结征文大赛活动

概念

将子节点渲染到指定的 DOM 节点,还可以当做子组件使用

父组件有 overflow: hiddenz-index 样式时,但你需要子组件能够在视觉上“跳出”其容器,例如,对话框、悬浮卡以及提示框

脱离文件流程

使用

<div>
  <SomeComponent />
  {createPortal(children, domNode, key?)}
</div>
import { createPortal } from 'react-dom';

export default function MyComponent() {
 return (
   <div style={{ border: '2px solid black' }}>
     <p>This child is placed in the parent div.</p>
     {createPortal(
       <p>This child is placed in the document body.</p>,
       document.body
     )}
   </div>
 );
}

modal dialog

import { useState } from 'react';
import { createPortal } from 'react-dom';
import ModalContent from './ModalContent.js';

export default function PortalExample() {
  const [showModal, setShowModal] = useState(false);
  return (
    <>
      <button onClick={() => setShowModal(true)}>
        Show modal using a portal
      </button>
      {showModal && createPortal(
        <ModalContent onClose={() => setShowModal(false)} />,
        document.body
      )}
    </>
  );
}

不是 react 组件内部使用

import { createPortal } from 'react-dom';

const sidebarContentEl = document.getElementById('sidebar-content');

export default function App() {
 return (
   <>
     <MainContent />
     {createPortal(
       <SidebarContent />,
       sidebarContentEl
     )}
   </>
 );
}

function MainContent() {
 return <p>This part is rendered by React</p>;
}

function SidebarContent() {
 return <p>This part is also rendered by React!</p>;
}

第三方组件库渲染 React 组件

import { useRef, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { createMapWidget, addPopupToMapWidget } from './map-widget.js';

export default function Map() {
 const containerRef = useRef(null);
 const mapRef = useRef(null);
 const [popupContainer, setPopupContainer] = useState(null);

 useEffect(() => {
   if (mapRef.current === null) {
     const map = createMapWidget(containerRef.current);
     mapRef.current = map;
     const popupDiv = addPopupToMapWidget(map);
     setPopupContainer(popupDiv);
   }
 }, []);

 return (
   <div style={{ width: 250, height: 250 }} ref={containerRef}>
     {popupContainer !== null && createPortal(
       <p>Hello from React!</p>,
       popupContainer
     )}
   </div>
 );
}

注意

domNode 必须已经存在