首页 > 解决方案 > 在 React Portal 上处理大量资源/DOM

问题描述

我在我的应用程序中创建了一个反应门户来处理 Modal 的使用。门户目标在我的 React 根 div 之外,作为我的根元素的兄弟。

<html>
   <body>
      <div id="root">{{app.html}}</div>
      <div id="modal-root">
         <div class="modal" tabIndex="-1" id="modal-inner-root" role="dialog">
         </div>
      </div>
   </body>
</html>

所以我的门户内容呈现在反应应用程序之外并且它工作正常。这是我的反应门户代码

const PortalRawModal = (props) => {
    const [ display, setDisplay ] = useState(document.getElementById("modal-inner-root").style.display)
    const div = useRef(document.createElement('div'))

    useEffect(()=> {
        const modalInnerRoot = document.getElementById("modal-inner-root")
        if(validate(props.showModalId)) {
            if( props.showModalId == props.modalId && _.size(props.children) > 0 ) {
                setDisplay("block");
                if(_.size(modalInnerRoot.childNodes) > 0) {
                    modalInnerRoot.replaceChild(div.current,modalInnerRoot.childNodes[0]);
                } else {
                    modalInnerRoot.appendChild(div.current);
                }
                div.current.className = props.modalInner;
                document.getElementById("modal-root").className = props.modalClassName;
                document.body.className = "modal-open";
            } else {
                document.getElementById("modal-root").className = props.modalClassName;
                if(div.current.parentNode == modalInnerRoot) {
                    modalInnerRoot.removeChild(div.current);
                    div.current.className = "";
                }
            }
        } else {
            setDisplay("none");
            document.getElementById("modal-root").className = "";
            if(div.current.parentNode == modalInnerRoot) {
                modalInnerRoot.removeChild(div.current).className = "";
            }
            document.body.className = "";
        }
    },[ props.showModalId ])

    useEffect(()=> {
        document.body.className = display == "none" ? "" : "modal-open";
        document.getElementById("modal-inner-root").style.display = display;

        return () => {
            if(!validate(props.showModalId)) {
                document.body.className = "";
                document.getElementById("modal-inner-root").style.display = "none"
            }
        };
    },[ display])

    useEffect(()=> {
        if(_.size(props.children) <= 0){
            modalInnerRoot.removeChild(div.current)
            document.body.className = "";
            document.getElementById("modal-inner-root").style.display = "none";
        }

        return () => {
            if(_.size(props.children) <= 0){
                modalInnerRoot.removeChild(div.current)
                document.body.className = "";
                document.getElementById("modal-inner-root").style.display = "none";
            }
        }
    },[props.children, props.showModalId])

    return ReactDOM.createPortal(props.children ,div.current);
}

每当传递孩子并挂载模态框时,都会毫不拖延地绘制沉重的 DOM。但是相同的标记需要时间,甚至会使浏览器选项卡崩溃。我在处理繁重的 DOM 操作时哪里出错了?或者有什么async方法可以处理不会影响整体性能的繁重 DOM 操作?

标签: reactjsreact-portal

解决方案


有几个原因可以归因于此:

  • 最后一个效果将始终作为对象在每次重新渲染时运行props.children,因此即使再次传递相同的子对象,它也将是一个新对象。
  • 直接 DOM 操作是一种反模式,因为 React 在内存中维护了几个 DOM 引用以便快速区分,因此直接突变可能会导致一些性能命中。尝试以声明性方式编写相同的内容。
  • 将门户内容提取到另一个子组件中,并尽可能避免 DOM 操作。

一个地方是:

if (_.size(props.children) <= 0) {
  modalInnerRoot.removeChild(div.current);
}

可以在渲染函数中替换,例如:

{React.Children.count(props.children) ? <div /> : null}

推荐阅读