React中元素跨父组件移动时如何保留其标识?
在React中跨父组件移动元素时保留标识的可行方案
你遇到的核心问题是:当Child在Parent1和Parent2之间切换时,React的diff算法会判定这是两个不同位置的元素(父节点在组件树中发生了变化),因此会卸载旧的Child并重新挂载新实例,导致video元素被销毁重建,播放卡顿。
之前尝试的方法无效的原因:
- Memo化只能防止重渲染,但无法阻止组件被卸载重建;
key仅在同一父组件的子元素列表中生效,跨父组件时React仍会判定为不同元素;- 传递
children本质上还是让Child的父节点在组件树中切换,依然会触发卸载。
可行方案:使用React Portal
Portal可以让组件在React组件树中保持父节点不变,仅将DOM节点挂载到不同的目标容器中。这样Child不会被卸载重建,DOM节点只是移动位置,video的播放状态完全保留。
具体实现步骤:
- 将
Child从Parent1/Parent2的子节点中抽离,放在外层组件中; - 在每个父组件内部预留一个用于挂载
Child的容器节点,并通过ref获取其DOM元素; - 根据
condition()的结果,使用ReactDOM.createPortal将Child渲染到当前激活的父容器中。
示例代码:
import { useState, useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; const Parent1 = ({ children }) => { return ( <div> <span>Hello I am parent 1</span> {children} </div> ); }; const Parent2 = ({ children }) => { return ( <div> <span>Hello I am parent 2</span> {children} </div> ); }; const Child = () => { return <video src="your-video-url" controls />; }; const MyComponent = () => { const [useParent1, setUseParent1] = useState(condition()); const parent1Container = useRef(null); const parent2Container = useRef(null); // 监听condition变化,更新父组件状态 useEffect(() => { const currentCondition = condition(); if (currentCondition !== useParent1) { setUseParent1(currentCondition); } }, []); // 确定当前要挂载Child的DOM容器 const targetContainer = useParent1 ? parent1Container.current : parent2Container.current; return ( <div> {/* 条件渲染父组件,每个父组件内预留容器 */} {useParent1 ? ( <Parent1> <div ref={parent1Container}></div> </Parent1> ) : ( <Parent2> <div ref={parent2Container}></div> </Parent2> )} {/* 通过Portal将Child渲染到目标容器 */} {targetContainer && ReactDOM.createPortal(<Child />, targetContainer)} </div> ); };
原理说明
- 在React组件树中,
Child的父节点始终是MyComponent的外层div,不会随父组件切换而改变; - Portal仅改变
Child的DOM节点挂载位置,React不会卸载或重建Child组件实例; video元素的DOM节点只是从一个父容器移动到另一个,播放状态(进度、播放/暂停等)完全保留。
内容的提问来源于stack exchange,提问作者Petr Smital
相关产品推荐
相关产品推荐

