React中如何避免Widget组件随condition属性变化反复挂载卸载?
避免Widget组件在condition变化时卸载重挂载的方案
当修改Foo组件的condition属性时,<Widget/>会被卸载后重新挂载,核心原因是:React的diff算法会将不同父组件下的Widget判定为两个独立的组件实例——它一会儿是<ComponentA>的子节点,一会儿又变成<ComponentB>内与<ComponentC>同级的子节点,因此React会销毁旧实例并创建新的。
以下是两种可行的解决方案:
方法一:使用React Portal保持DOM节点稳定
Portal允许组件渲染到DOM树的任意位置,不受父组件结构变化的影响。我们可以让Widget始终渲染到一个固定的DOM容器中,再根据condition将这个容器挂载到对应的父组件里,这样Widget的实例和DOM节点都不会被销毁。
import React, { useRef, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; interface FooProps { condition: boolean; } const Foo: React.FC<FooProps> = ({ condition }) => { // 创建一个固定的DOM容器用于存放Widget const widgetContainer = useRef<HTMLDivElement>(document.createElement('div')); // 用于追踪当前要挂载容器的父元素 const [targetContainer, setTargetContainer] = useState<HTMLElement | null>(null); useEffect(() => { if (targetContainer && widgetContainer.current) { // 将Widget容器挂载到目标父元素中 targetContainer.appendChild(widgetContainer.current); } return () => { // 组件卸载时清理容器 widgetContainer.current.remove(); }; }, [targetContainer]); return ( <> {condition ? ( <ComponentA ref={(el) => setTargetContainer(el as HTMLElement)} /> ) : ( <ComponentB> <ComponentC /> {/* 作为Widget容器的挂载占位符 */} <div ref={(el) => setTargetContainer(el as HTMLElement)} /> </ComponentB> )} {/* 通过Portal将Widget渲染到固定容器 */} {ReactDOM.createPortal(<Widget />, widgetContainer.current)} </> ); };
方法二:统一Widget的父容器结构
如果Portal的实现过于复杂,可以调整组件结构,让Widget始终处于同一个逻辑父容器下,通过动态切换父容器的包裹组件来实现需求。这种方式下,React会识别到Widget是同一个实例,不会触发卸载重挂载。
interface FooProps { condition: boolean; } const Foo: React.FC<FooProps> = ({ condition }) => { // 根据condition动态选择包裹组件 const WidgetWrapper = condition ? ComponentA : ({ children }) => ( <ComponentB> <ComponentC /> {children} </ComponentB> ); return ( <WidgetWrapper> <Widget /> </WidgetWrapper> ); };
注意:这种方法会导致<ComponentB>和<ComponentC>在condition为true时被卸载,condition为false时重新挂载。如果你的业务需要保持<ComponentC>的状态,建议使用第一种方法。
内容的提问来源于stack exchange,提问作者A.A
相关产品推荐
相关产品推荐

