ReactDOM.createPortal() 在 next.js-typescript 中创建额外的空白 div

ReactDOM.createPortal() is creating extra blank divs in next.js-typescript

这是backdrop.tsx:

interface BacdropProps {
  open?: string;
  onClick: () => void;
}

const Backdrop: React.FC<BacdropProps> = (props) => {
  let container: HTMLDivElement | null = null;
  if (typeof window !== "undefined") {
    const rootContainer = document.createElement("div");
    const parentElem = document.querySelector("#__next");
    parentElem?.insertAdjacentElement("afterend", rootContainer);
    // parentElem?.after(rootContainer) this gives me same issue
    container = rootContainer;
  }

  return container
    ? ReactDOM.createPortal(
        <div
          className={["backdrop", props.open ? "open" : ""].join(" ")}
          onClick={props.onClick}
        />,
        container
      )
    : null;
};

export default Backdrop;

这是 css Backdoor.tsx

.backdrop {
  width: 100%;
  height: 100vh;
  background: rgba(0, 0, 0, 0.75);
  z-index: 100;
  position: fixed;
  left: 0;
  top: 0;
  transition: opacity 0.3s ease-out;
  opacity: 1;
}

这是它的样子:

您的代码将在每次 Backdrop 重新呈现时创建 div.backdrop。正确的方法应该是创建一次。正确的方法是使用 useEffect 承诺 ReactDOM.createPortal 只执行一次。并且还应用 useRef 以确保 container 在每个渲染中保持相同的实例。

const containerRef = useRef<HTMLDivElement>(null);

useEffect({
  // Will be execute once in client-side
  if (typeof window !== "undefined") {
    const rootContainer = document.createElement("div");
    const parentElem = document.querySelector("#__next");
    parentElem?.insertAdjacentElement("afterend", rootContainer);
    // parentElem?.after(rootContainer) this gives me same issue
    containerRef.current = rootContainer;
  }
}, [window])

useEffect({
  // Will be execute once when containerRef is bind to <HTMLDivElement>
  if(containerRef.current) {
    ReactDOM.createPortal(
      <div
        className={["backdrop", props.open ? "open" : ""].join(" ")}
        onClick={props.onClick}
      />,
      containerRef.current
    )
  }
}, [containerRef])

编辑

  1. 我删除了 window 中存在的检测,因为 useEffect 只会在客户端执行。

  2. 由于 ReactDOM.createPortal 将在根 HTMLElement (div#next) 之外创建 div.backdrop,我认为只是 return nullBackdrop 组件中是好的。

const containerRef = useRef<HTMLDivElement>(null);

useEffect({
  // useEffect would run just in client-side
  const rootContainer = document.createElement("div");
  const parentElem = document.querySelector("#__next");
  parentElem?.insertAdjacentElement("afterend", rootContainer);
  // parentElem?.after(rootContainer) this gives me same issue
  containerRef.current = rootContainer;
}, [])

useEffect({
  // Will be execute once when containerRef is bind to <HTMLDivElement>
  if(containerRef.current) {
    ReactDOM.createPortal(
      <div
        className={["backdrop", props.open ? "open" : ""].join(" ")}
        onClick={props.onClick}
      />,
      containerRef.current
    )
  }
}, [containerRef])

return null;