前言
合理拆分组件是 React 开发中的重要一步,当我们遇到比较大的组件时,我们会对其拆分为数个小组件,在声明和使用小组件时有多种方式,今天我们把各种方式列举出来并且进行讨论,里面会包含一些anti-pattern(反面模式),大家在日常开发中要避开这些anti-pattern。
⛔️ 不要在组件内部声明组件
const BigComponent = () => {
const SmallCompoent = () => {
return <div>SmallCompoent</div>;
};
return (
<SmallCompoent />
);
};
在组件内部声明组件是一种不好的声明方式,会导致性能上的问题。在每次重新渲染时,React 都会重新卸载、挂载这个组件(SmallCompoent),即销毁并重新创建它,这会比正常的重新渲染开销更多,并且每次重新渲染都会重置这个组件内的状态。
这种方式已经被公认为反面模式,如果想避免在项目里出现这种方式,可以使用 ESLint 的 react/no-unstable-nested-components 规则。
大家可以看看以下链接的例子,页面上有 2 个 Toggle 按钮,当点击 Toggle 的时候,Toggle Count 会统计点击次数,A Room 是直接写在 return JSX 里面,B Room 则是在组件内部定义了一个组件 ToogleWithTitle。
这时候我们分别点击 2 个 Toggle 按钮会发现,A Room Toggle 状态表现正常,但是 B Room Toggle 状态没有发生改变,因为 App 每次渲染的时候,都会重新卸载、挂载Toggle,导致状态也会重置。
🤔 存放JSX的变量
const BigComponent = () => {
const smallCompoent = <div>SmallCompoent</div>;
return (
{smallCompoent}
);
};
在一些特定的情景下,例如我想保持return JSX的干净、可读性,会把一些凌乱的JSX单独放到一个组件内的变量里面,这时候变量smallCompoent属于BigComponent组件return JSX的一部分,不会影响React Diff算法,因为smallCompoent不属于一个组件,没有组件树,没有自己的生命周期、状态,会跟着BigComponent组件一起渲染。
🤔 返回JSX的函数
const BigComponent = () => {
const renderSmallCompoent = () => {
return <div>SmallCompoent</div>;
};
return (
{renderSmallCompoent()}
);
};
这个使用方式和上述“存放JSX的变量”非常相似,只不过函数方式支持参数的传入,renderSmallCompoent也是属于BigComponent组件return JSX的一部分,不会影响React Diff算法,没有自己的组件树,没有自己的生命周期、状态,会跟着BigComponent组件一起渲染。
不过这种使用方式争议比较大,ESLint有几个Issue提出禁用这种使用方式,不过最后都没有合并,主要的观点是扩展性,如果在未来要增强这个“返回JSX函数”,让它有自己的状态或生命周期,会需要花费较大的成本去改造他,不如现阶段直接把它拆分为一个真正的组件。
Rule to forbid calling components as functions? #3208
Rule Proposal: functions returning JSX should be refactored into React components #578
我的个人观点是,毕竟这种使用方式是没有危害的,不会影响React Diff算法,如果这个"小组件"在短期内比较小,而且没有自己的生命周期和状态,那你就大胆放心使用吧,不过还需要考虑团队的规范,不要增加code review成本。
✅ 在组件外声明组件
const SmallCompoent = () => {
return <div>SmallCompoent</div>;
};
const BigComponent = () => {
return (
<SmallCompoent />
);
};
最好的方式还是遵循 React 官方的建议,根据单一职责原则、最小状态集拆分组件,让组件以最小范围渲染。
Thinking in React