【问题标题】:ReactDOM.createPortal() is creating extra blank divs in next.js-typescriptReactDOM.createPortal() 在 next.js-typescript 中创建额外的空白 div
【发布时间】:2021-04-25 12:46:22
【问题描述】:

这是背景.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;

这是 Backdoor.tsx 的 css

.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;
}

这是它的外观:

【问题讨论】:

    标签: css reactjs typescript next.js react-portal


    【解决方案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,我认为只需在Backdrop 组件中返回null 即可。

    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;
    

    【讨论】:

    • 感谢您的回答。我有 2 个问题。我们为什么要使用 [window]。目前它说“窗口”没有定义。看起来它是在服务器上呈现的。第二期,我要在组件中返回什么?
    • 因为我的问题是询问如何解决额外 div 的问题,而您处理了这个问题。我接受了你的回答并喜欢这个答案。但我有另一个组件模式,它安装在 dom 上,但它没有显示在屏幕上。你能查一下这个问题吗:stackoverflow.com/questions/65914360/…