别仅仅因为喜欢就添加 React 组件

别仅仅因为喜欢就添加 React 组件

如何通过质疑每一个新抽象来做出更好的架构决策

引发这一切的标题

现在各地的代码库中都在发生这样的对话。有人提议增加一个新的 React 组件。这看起来似乎无害。但随后另一个人问道:我们真的需要这个吗?答案通常让人惊讶。

在2026年7月14日发表的一篇文章中,DEV Community 的一位开发者描述了他们代码库中的一次架构讨论。几个同事想引入一个新的 Heading 组件。代码看起来会是这样的:

<Heading level={3}>Profile Settings</Heading>

它很干净。具有语义化。感觉井井有条。但这里存在一个问题。

我们已经有了三种解决方案

团队已经可以这样写:

<h3>Profile Settings</h3>

而且他们也可以使用现有的、专为此类灵活性而构建的 Text 组件:

<Text as="h3">Profile Settings</Text>

那么为什么要添加第四种方法呢?当被问及此事时,回答很诚实:“我喜欢我们有一个单独的 heading 组件。”

这是一个人类层面的原因。这不是一个坏理由。但它不是一个工程层面的原因。

个人偏好不是设计原则

前端团队热爱抽象。有时候有些过了头。包装器被进一步包装。在这些之上的包装器再次被包装。最初的 HTML 变得面目全非。

这些辩护听起来很熟悉:“它看起来更干净。”“感觉更一致。”“我喜欢它。”

这些是可以理解的。它们能引起共鸣。但它们是软弱的工程理由。原因如下:每一个新组件都要付出代价。

  • 要编写的文档
  • 要维护的代码
  • 要保持最新的测试
  • 要培训的新开发者
  • 当事物变化时的迁移路径
  • 让团队困惑的 API 重叠

共享的代码库不是任何人的个人沙盒。每一个抽象都会成为你的团队长期负担的一部分。这种负担需要比偏好更有力的理由。

HTML 已经是一个精心设计的 API

我们有时会忘记这一点。HTML 不是原始的。它并不粗糙。它是一个经过深思熟虑的工程化抽象,你可以在其之上构建——但并不总是要取代它。

以标题为例。原生的层级结构已经为你提供了:

  • 语义含义(h1 比 h3 更重要)
  • 内置的可访问性支持
  • 屏幕阅读器兼容性
  • SEO 上下文
  • 文档大纲结构

所有这些都不是副作用。它是被设计进标准中的。

当你像这样把它包装在一个自定义组件中时:

<Heading level={1}>Dashboard</Heading>
<Heading level={2}>Analytics</Heading>
<Heading level={3}>Revenue</Heading>

在大多数情况下,你并没有改善输出。你只是在改变语法。而在不改变行为的情况下改变语法,很少值得付出成本。

改变一切的一条规则

以下是应指导这些决定的原则: 不要抽象语法。要抽象行为。

就是这样。这一个区分消灭了大多数不必要的争论。

一个糟糕的抽象过于模仿原生的 HTML:

<Heading level={3}>Title</Heading>

为什么这很糟糕?因为它主要模仿了 h3。它没有添加任何新东西。

一个好的抽象添加了真正的行为:

<Text variant="muted" size="small" truncate>
  Description
</Text>

这集中了:

  • 设计令牌映射
  • 文本截断逻辑
  • 主题一致性
  • 响应式行为

这就是价值。这值得付出抽象成本。

一个概念应该拥有一个 API

困惑在代码库中传播得很快。这里是一个完美的滋生地:

<Heading level={3}>Profile</Heading>
<Text as="h3">Profile</Text>
<h3>Profile</h3>

编写同一件事的三种方式。现在每一个拉取请求都会变成一场风格辩论。“我们应该使用 Heading 还是 Text 还是原生的 h3?”没人真正知道。

优秀的系统减少选择。一个有用的原则: 一个概念,一个 API。 如果 HTML 已经处理了语义,那就让它掌管语义。如果你的 Text 组件掌管排版,那就让它掌管排版。清晰的分离。清晰的所有权。

组合胜过抽象

与其为特性的每一种组合创建一个包装器,不如优先使用组合。

避免这样:

<Heading level={2}>Billing Settings</Heading>

更推荐这样:

<h2>
  <Text variant="secondary">
    Billing Settings
  </Text>
</h2>

你得到了:

  • 原生 HTML 语义(h2 依然意味着 h2 的意思)
  • 可重用的排版令牌(Text 变体处理样式)
  • 清晰的责任(h2 负责结构,Text 负责外观)
  • 没有重叠的 API
  • 没有歧义

比起另一层抽象,组合更加灵活并且增加了更少的困惑。

构建前的五个问题

在提出一个新组件之前,问问自己:

1. 这实际上解决了什么问题? 不要说“看起来更干净”或“我喜欢这样。”而要是一些可衡量的东西,比如“强制实施可访问的层级结构”或“防止字体大小不一致。”

2. HTML 已经解决它了吗? 如果是:为什么要取代它?那种负担需要认真的理由。

3. 它减少了复杂性吗? 或者它只是将复杂性转移并隐藏在一个组件中?

4. 它会引入 API 重叠吗? 重叠的做事方式会造成不一致的代码库。那是一种成本,而不是一项特性。

5. 它增加了行为吗? 如果该组件只是改变了语法而没有增加行为,它可能不应该存在。

结论

不是每一个重复的模式都值得拥有一个组件。不是每一个 HTML 元素都需要一个 React 包装器。并且不是每一种偏好都值得成为一个共享的抽象。真正重要的问题是:这个抽象是在创造新能力,还是仅仅是新语法?诚实地回答这个问题,你就会保护你的代码库免受多年来无人真正需要的复杂性的困扰。

优点

  • 为决定组件何时解决真正问题提供了可衡量的标准
  • 保留了原生 HTML 元素的可访问性和语义价值
  • 减少了因完成相同任务的多种方式而引起的团队困惑
  • 将组件创建的重点放在增加新行为上,而不仅仅是改变语法
  • 鼓励组合,这比包装器抽象更灵活
  • 有助于防止代码库被掩埋在一层层不必要的组件之下

缺点

  • 更喜欢构建自定义抽象的开发者可能会觉得这种方法具有限制性
  • 已经拥有许多自定义组件的代码库面临着重大的重构工作
  • 确定什么算作“行为”与“语法”需要持续的团队讨论和共识
  • 组合模式有时让人感觉不如层次化的组件结构井然有序

注意

本文解释了来自已发表来源的架构原则。代码示例仅为说明性,并使用占位符 JSX 语法。在将这些原则应用于你自己的代码库之前,请针对原始来源验证这些声明,并将该指南适应于你团队的特定背景和现有模式。每个团队的情况都不同;对一个团队有效的方法可能需要为另一个团队进行调整。应始终与你的团队讨论架构决策,而不是单方面地应用规则。

常见问题

什么时候我应该创建自定义组件而不是使用 HTML? — 当组件增加诸如设计令牌、响应式逻辑或集中样式等行为时,创建它们。如果组件仅改变语法而不添加逻辑,通常原生 HTML 或组合效果更好。

我如何区分好的抽象和坏的抽象? — 问一问:这个组件是否解决了 HTML 尚未解决的问题?它是否集中了行为或逻辑?如果是,它可能是好的。如果它只是包装 HTML 而没有增加能力,它可能是不必要的。

如果我的团队已经有太多自定义组件怎么办? — 从识别哪些组件增加了实际行为、哪些只是包装 HTML 语法开始。保留那些解决真正问题的组件。对于仅有语法的包装器,逐步迁移到原生 HTML 或组合。

为什么组合比包装器组件更好? — 组合保留了原生的 HTML 语义,清晰地重用了设计令牌,并保持了关注点分离。它也更灵活,因为你可以将许多聚焦的小部件结合起来,而不是被锁定在一个包装器所允许的内容中。

关于设计系统一致性呢? — 设计系统应该定义行为和设计令牌(颜色、字体、间距),而不是创建包装器。让开发者用原生元素或最小的抽象来组合那些令牌。

我如何向我的团队解释这种方法? — 将其作为针对未来组件的决策框架,而不是对现有组件的批评。重点关注每一个组件所带来的成本:文档、测试和维护。就什么样的行为能够证明添加新组件是合理的达成一致。

标签

#react #frontend #architecture #components #webdev #designsystems #abstraction #codemaintenance

Free field guide

API Security Testing Checklist

A practical workflow for testing authentication, authorization, input handling, business logic, and evidence without losing track of scope.