文章批判了React开发中过度追求组件复用的现象,认为这导致了代码库的臃肿和维护困难。作者提倡“后复用”心态,强调清晰、独立和上下文感知的组件设计,认为适度的代码重复比过度抽象更有利于实际开发。
译自:The React Component Pyramid Scheme: An Over-Engineering Crisis
作者:Alexander T. Williams
不知何时起,我们不再编写 React 组件,而是开始崇拜它们。“可复用性”的口号从最佳实践变成了完整的教条。
每个团队都有那么一个工程师,宣扬着“终极组件”的福音——这个组件可以通过大量的可选 props 和 500 行的条件语句来处理各种布局、颜色方案和边缘情况。
原本是一个高尚的、旨在实现更清晰架构的目标,却演变成了一个失控的代码库,其中每个文件都导入其他所有文件,没有人敢触碰基础组件,生怕破坏整个应用程序。这不是代码复用;这是将依赖债务披上美德的外衣。
组件复用教
开发者热爱优雅。我们渴望那些能让我们感觉超越了临时编码混乱的模式。“不要重复自己”(DRY)原则成了一个神圣的原则——一个简单的想法演变成了教条。许多团队为了避免不必要的重复,开始避免任何重复,无论付出什么代价。结果呢?抽象的怪物,它们能将一千件事做得糟糕透顶,而不是将三件事做得出色。
对可复用性的痴迷常常导致“props soup”。
对可复用性的痴迷常常导致“props soup”:组件被过多的 props 淹没,以处理所有可能的变体。你会看到 Button 组件接受 20 个选项,仅仅是为了微调内边距或图标对齐。这些组件非但没有简化开发,反而增加了大规模的复杂性。调试变成了考古挖掘,开发者在抽象层层叠叠中摸索,试图弄清楚为什么一个按钮在一个上下文中看起来不对,而在另一个上下文中却看起来正常。
讽刺的是,这种对复用的迷恋实际上降低了实际的复用。当一个组件变得如此通用和臃肿,以至于没有人愿意触碰它时,开发者还是会选择克隆它。
当 DRY 原则导致复杂性时
DRY 原则理论上很美好,但实践起来却很脆弱。代码重复并非总是邪恶的;有时它只是务实的。过度抽象会剥离代码的上下文,使其更难理解。重复几行代码通常比花费一个月的时间来解读一个可复用逻辑的迷宫更便宜。不要成为指标的奴隶,人们。让它们为你服务。
想象一下你的应用程序中有两个表单:一个登录表单和一个注册表单。它们共享一些输入,但具有不同的验证逻辑和提交流程。DRY 的狂热者会将它们合并成一个单一的巨型组件,带有一打条件分支。现在,每一个小的改动都有可能破坏两个流程。原始的两个简单组件已经合并成了一个 if 语句的混合体。
关键不在于无休止地重复自己,而在于知道何时复用不再高效,何时开始变成纠缠。
良好的工程在于平衡。DRY 应该与另一个鲜为人知的原则共存:WET——每次编写。故意重复可以保持清晰。它允许代码独立演进。
关键不在于无休止地重复自己,而在于知道何时复用不再高效,何时开始变成纠缠。React 的组件模型足够灵活,可以容忍一些重复。问题在于开发者将重复视为罪恶而非策略。
过度设计的按钮:一个案例研究
让我们谈谈 Button.jsx,它是过度设计的非官方吉祥物。它总是在那里,潜伏在每个 React 项目中。它开始很简单:一个可复用的按钮,带有一个颜色和一个标签。然后有人需要一个图标。另一个人需要一个加载状态。
然后它必须支持链接、禁用状态、嵌套组件、主题和自定义点击处理程序。很快,Button.jsx 就有了十几个 props,大量的条件渲染,以及比组件本身还要长的 propType 列表。
每个新开发者都会被警告:“不要触碰 Button.jsx。”然而每个人都会去触碰。
每个新开发者都会被警告:“不要触碰 Button.jsx。”然而每个人都会去触碰,因为每个新功能都需要多一次的调整。最终,抽象由于自身的灵活性而崩溃。测试变得痛苦,因为你实际上是在测试塞在一个文件里的 10 个不同的组件。文档滞后,而最初高尚的复用尝试现在却扼杀了生产力。
在那时,团队开始分裂——创建 SlightlyDifferentButton.jsx 或 IconButton.jsx。讽刺的是,过度复用的解药是重复。代码库回到了一个实际上更易于维护的状态,因为每个按钮现在都能做好一件事。启示是:复用应该服务于清晰度,而不是反过来。
过早抽象的危险
复用的神话源于对代码可维护性的误解。可维护性不在于你写了多少行代码以及是否减少了云成本,而在于理解代码需要多少次心智跳转。每一次抽象都会增加一个间接层,而每一层都会消耗认知努力。问题不在于我们进行抽象,而在于我们过早地进行抽象——在我们真正理解问题领域之前。
每一个抽象都会增加一个间接层,而每一层都会消耗认知努力。
开发者经常过早地进行泛化,因为它感觉很高效。“我们可能需要在另一个模块中使用它,”有人说,然后一个单一用途的 hook 就变成了一个拥有五个参数和两个上下文提供者的实用工具。但没有经过实际验证的复用的抽象不是远见;它是投机。而投机性的抽象是很难删除的代码,因为它们将来可能仍然有用。
伟大的抽象是自然产生的。它们诞生于在真实用例中反复出现的模式。当你进行抽象并正确使用逃生舱口,在看到三四个相同的问例后,你的解决方案就包含了智慧而非假设。而过早的抽象则创建了一个脚手架,将你的代码困在不再有意义的模式中。
采纳“后复用”心态来构建 React 架构
我们需要对“好的”React 架构是什么样子进行一次文化重塑。行业对 DRY、通用组件和高级抽象的痴迷导致了脆弱的系统,这些系统看起来优雅但容易出错。真正的可维护性不在于你能共享多少行代码,而在于在隔离状态下理解每个部分有多容易。
信不信由你,拥抱重复并不意味着回到混乱。它意味着尊重边界,让代码更接近其上下文,并接受本地优化通常优于全局纯粹性。在实践中,这可能意味着拥有三个看起来相似度达到 80% 的表单组件,但它们易于独立修改。它可能意味着删除一个只使用过两次但被认为是“实用”的函数。它甚至可能意味着删除你的通用 Button.jsx 并重新开始。
不知何时起,我们用对抽象的崇拜取代了简洁。
React 是建立在小型、可组合的部件理念之上的。不知何时起,我们用对抽象的崇拜取代了这种简洁。是时候重新发现简单、可读、上下文感知代码的乐趣了。最好的组件不是那个你能随处使用的组件——而是那个你能瞬间理解的组件。
简单、独立的组件
React 生态系统不需要更多可复用的组件和存储它们的技术。它需要更多有意识设计的组件。对复用本身的追求创造了一代理论上优雅但实践中脆弱的代码库。每一个抽象都有成本,而账单总有一天会到来。
复用是一种工具,而非宗教。下次当你想要将三个相似的组件重构为一个“智能”的抽象时,问问自己:这是为了服务代码还是服务你的自尊?因为归根结底,清晰比机巧更持久。
最健康的系统不是建立在抽象层上的金字塔;它们是简单、独立组件的花园,可以独立生长,而不会相互扼杀。也许是时候停止攀登金字塔——而是开始修剪花园了。