切换React移动端/桌面端布局时,如何阻止子组件重复渲染?
问题
我有移动端和桌面端两种布局,结构不同但都会注入相同的子组件。希望在布局切换时阻止子组件重渲染,尝试过React.memo包裹子组件、在Parent组件内使用useMemo都无效。复现代码如下:
import React, { useState } from "react"; interface ParentProps { children: React.ReactNode; } const Parent = ({ children }: ParentProps): JSX.Element => { const [value, setValue] = useState(false); if (value) { return ( <> <button type="button" onClick={() => setValue((val) => !val)}> Turn into false </button> {children} </> ); } return ( // 桌面端额外元素 <div> <div> <button type="button" onClick={() => setValue((val) => !val)}> Turn into true </button> {children} </div> </div> ); }; const ChildrenComponent = () => { console.log("rerender Children..."); return <p>children component</p>; }; export const App = ()=> { return ( <Parent> <ChildrenComponent /> </Parent> ); }
原因分析
你遇到的不是单纯的重渲染问题,而是布局切换时React会卸载旧的子组件并重新挂载:当Parent的state变化,返回的JSX结构完全不同(一个是<>包裹,一个是嵌套<div>),React的Diff算法会认为这是两棵完全不同的树,因此会销毁原有子组件实例,重新创建新的实例。这时候React.memo和useMemo都起不到作用——因为它们只能阻止组件在props不变时的重渲染,无法阻止卸载再挂载。
解决方案
方法1:给子组件容器添加稳定的key,配合React.memo
让React识别出两个布局分支里的子组件容器是同一个元素,避免卸载再挂载,同时用React.memo阻止子组件不必要的重渲染:
- 用
React.memo包裹子组件:
const ChildrenComponent = React.memo(() => { console.log("rerender Children..."); return <p>children component</p>; });
- 修改Parent组件,在两个布局分支里给子组件包裹同一个带稳定
key的容器:
const Parent = ({ children }: ParentProps): JSX.Element => { const [value, setValue] = useState(false); if (value) { return ( <> <button type="button" onClick={() => setValue((val) => !val)}> Turn into false </button> {/* 统一容器key */} <div key="child-wrapper">{children}</div> </> ); } return ( <div> <div> <button type="button" onClick={() => setValue((val) => !val)}> Turn into true </button> {/* 统一容器key */} <div key="child-wrapper">{children}</div> </div> </div> ); };
方法2:用useMemo保持子组件引用稳定(可选)
如果需要进一步确保children的引用不随Parent重渲染变化,可以在Parent中用useMemo缓存children:
const Parent = ({ children }: ParentProps): JSX.Element => { const [value, setValue] = useState(false); // 缓存children,避免Parent重渲染时重新创建引用 const stableChildren = useMemo(() => children, []); if (value) { return ( <> <button type="button" onClick={() => setValue((val) => !val)}> Turn into false </button> <div key="child-wrapper">{stableChildren}</div> </> ); } return ( <div> <div> <button type="button" onClick={() => setValue((val) => !val)}> Turn into true </button> <div key="child-wrapper">{stableChildren}</div> </div> </div> ); };
方法3:使用React.Children处理单个子组件(适用于明确只有一个子组件的场景)
如果确定Parent只接收一个子组件,可以用React.Children.only确保引用稳定,结合React.memo:
const Parent = ({ children }: ParentProps): JSX.Element => { const [value, setValue] = useState(false); const onlyChild = React.Children.only(children); if (value) { return ( <> <button type="button" onClick={() => setValue((val) => !val)}> Turn into false </button> <div key="child-wrapper">{onlyChild}</div> </> ); } return ( <div> <div> <button type="button" onClick={() => setValue((val) => !val)}> Turn into true </button> <div key="child-wrapper">{onlyChild}</div> </div> </div> ); };
验证
修改后点击切换按钮,子组件的console.log不会再触发,说明既没有重渲染也没有被卸载再挂载。
内容的提问来源于stack exchange,提问作者Purpurovv
相关产品推荐
相关产品推荐

