如何重写React组件树以发挥React.memo作用,避免条件包裹时的重渲染
问题根因
你遇到的现象本质不是React.memo失效:当包裹条件变化时,同一个列表索引位置的节点类型从<Child />变成了<div>,React的协调算法会直接卸载原有节点、挂载新节点,这属于组件的销毁重建流程,不属于重渲染范畴,React.memo只能拦截props不变时的重渲染,管不到挂载销毁逻辑,因此不会生效。
方案1:保持组件树结构不变(最推荐)
不需要修改组件逻辑,只需要让React组件树结构全程保持一致,仅控制DOM层的表现即可完美解决问题:
永远渲染外层的div标签,不需要包裹时给它添加display: contents样式。该属性会让div不生成任何实际DOM盒模型,子元素的布局表现和直接放在父级完全一致,视觉上等于没有包裹层,但React组件树结构没有任何变化,<Child />不会被卸载,React.memo自然正常生效。
修改后的渲染部分代码:
{[0, 1, 2, 3, 4, 5].map((n) => ( <div key={n} style={shouldWrapChildComponent ? undefined : { display: 'contents' }} > <Child n={n} /> </div> ))}
方案2:必须动态切换包裹层的解法
如果业务确实需要在有div和无div之间切换真实DOM结构(比如display: contents不符合业务需求),可以通过稳定key+缓存组件实例的方式保留<Child />的渲染结果,避免销毁重建:
import React, { useState, memo, useMemo } from "react"; const Child = memo( ({ n }) => { console.log("Re-rendered child"); return <span>Child {n}</span>; }, () => true ); export default function App() { const [shouldWrapChildComponent, setShouldWrapChildComponent] = useState(false); // 提前缓存所有Child实例,仅初始化时生成一次 const childItems = useMemo( () => [0,1,2,3,4,5].map(n => ({ id: n, element: <Child n={n} key={`child-${n}`} /> })), [] ) return ( <div> <button onClick={() => setShouldWrapChildComponent( (prev) => !prev ) } > Toggle Wrapper </button> <br /> {childItems.map(({id, element}) => { return shouldWrapChildComponent ? ( <div key={id}>{element}</div> ) : element })} </div> ); }
内容的提问来源于stack exchange,提问作者Raphael Rafatpanah
相关产品推荐
相关产品推荐

