ReactDOM.createPortal

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReactDOM.createPortal相关的知识,希望对你有一定的参考价值。

参考技术A createPortal 的调用方式是:

第一个参数是一个 renderable React child
第二个参数是一个DOM元素

将index.html页面添加DOM节点来验证createPortal如何渲染

大白话的意思是:
通过createPortal渲染的元素会被添加到另外的节点,同时点击事件会被触发;
而通过ReactDOM.render渲染的元素添加到新节点,但是点击事件没有触发。

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

【中文标题】ReactDOM.createPortal() 在 next.js-typescript 中创建额外的空白 div【英文标题】:ReactDOM.createPortal() is creating extra blank divs in next.js-typescript 【发布时间】: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;

这是它的外观:

【问题讨论】:

【参考方案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])

编辑

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

    由于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 上,但它没有显示在屏幕上。你能查一下这个问题吗:***.com/questions/65914360/…

以上是关于ReactDOM.createPortal的主要内容,如果未能解决你的问题,请参考以下文章