React组件库实战 - 第一章:一文带你看懂现代组件库如何封装规范化的工程体系以及提交工作流,这个系列,带你手搓一个组件库!

第一章. 架构师的开篇:从组件库到设计系统

本章的核心目标是建立一个统一且深刻的认知框架。我们将要构建的,并非一个简单的 UI 组件集合,而是一个能够提升团队生产力、保障产品体验一致性、并作为公司数字产品核心资产的系统性工程解决方案。我们将首先精确定义“设计系统”的内涵与外延,深入分析其技术选型背后的逻辑,并最终构建一个标准化的、自动化的工程开发环境。这是后续所有高级实践的坚实基础。

1.1. 超越组件库:什么是“设计系统”?

在启动任何具体的编码工作之前,首要任务是校准我们对目标的理解。许多技术团队投入资源构建了内部的组件库,却发现随着业务扩张,产品不同模块间的视觉差异依旧越来越大,代码冗余度不降反升,设计与开发之间的协作鸿沟难以逾越。

这些问题的根源在于,一个孤立的、缺乏顶层设计与治理规范的组件库,其本质上仍是一个被动的工具集。而我们的目标,是构建一个更高维度的、主动的、具备自我演进能力的产物——设计系统。它是一种思想、一套流程和一个工程产品的有机结合体。

问题背景的深度剖析:
在一个高速发展且有一定规模的组织中,缺少设计系统所引发的问题是系统性的,并会随着时间推移被急剧放大。例如,市场部门为一次营销活动快速搭建的着陆页,其视觉风格、交互模式与主应用存在明显割裂,这不仅稀释了品牌形象,也增加了用户的认知负荷。在开发层面,不同业务线的团队可能基于不同的技术栈或 UI 理解,重复“发明轮子”,分别实现了功能类似的日期选择器或数据表格,导致了巨大的研发资源浪费和后续高昂的维护成本。当产品需要进行品牌升级,要求全局性地调整主色调和字体规范时,这项任务会演变成一场灾难性的、涉及多个代码仓库的、高风险的“替换工程”。

设计系统正是为了从根本上解决这一系列混乱而诞生的结构性解决方案。它将设计决策转化为可复用、可约束、可治理的工程资产,从而解放生产力。

1.1.1. 剖析设计系统的三大支柱

一个功能完备、可持续发展的设计系统,由三个相互支撑、紧密协作的核心支柱构成:设计语言、组件库、以及模式与文档。

首先,设计语言 是整个系统的理论基础与最高纲领。它是一套用于指导产品设计与开发的、体系化的、抽象的规则与美学愿景的集合。其核心是 设计原则,例如“清晰”、“高效”、“一致”等高阶准则,它们是进行具体设计决策时的重要依据。原则之下是更具体的 设计规范,例如无障碍可访问性规范(WCAG)、8 点栅格系统、动画缓动曲线定义等。而将这一切与工程实现连接起来的关键,是 设计令牌 (Design Tokens)。设计令牌是以平台无关的、结构化的变量形式存在的样式定义,是所有视觉表现的单一数据源。例如,一个颜色令牌可能被定义为 color.brand.primary.default,其值为 #007BFF。这种结构化的命名使其具备极高的可读性和可维护性。

设计令牌是平台无关的,这是其能够支持 Web、iOS 和 Android 等多端体验一致性的技术前提。

其次,组件库 是设计语言在代码层面的具象化、标准化实现。它是一套封装了样式、交互逻辑与状态管理的、经过完整测试的、可重用的用户界面工程实体。开发者通过调用这些标准化的组件来高效地构建应用界面。一个高质量的组件库不仅要实现设计稿的视觉效果,更要关注其工程属性。这包括提供设计精良的 API,在易用性与灵活性之间取得平衡;包含完备的 测试套件,覆盖单元、集成和视觉回归测试,以保证其健壮性;以及严格遵循 无障碍标准,确保所有用户都能无障碍地使用。

最后,模式与文档 是连接理论与实践、指导团队成员规模化应用设计系统的规范性文件与最佳实践的集合。它扮演着知识库和使用手册的角色,确保系统能够在团队中被正确理解和高效应用。其内容远超基础的组件 API 文档,核心在于沉淀 使用模式。例如,一个“用户认证模式”会详细规定登录和注册表单的整体布局、所需组件(Input, Button, Checkbox)、错误提示的展示方式、加载状态的处理逻辑等,为开发者提供可直接复用的“解决方案级”指引。此外,它还应包含 贡献与治理流程,明确新组件的提案、代码提交、版本发布以及组件废弃的管理办法,确保设计系统自身的健康演进。

为了更清晰地总结三者关系,我们可以参考下表:

支柱核心价值关键产出
设计语言理论与原则设计原则、规范文档、设计令牌集
组件库工程与实体NPM 包、标准化的 UI 组件
模式与文档指导与知识组件 API 文档、场景化使用模式、贡献流程

1.1.2. 明确我们在课程中扮演的角色:开发者如何与设计师协作

在设计系统驱动的工作流中,开发者的角色定位发生了根本性的转变。开发者不再是设计流程下游的被动执行者,而是以系统共建者的身份,深度参与到从定义到实现的全过程。这种转变要求开发者具备更强的 工程产品化 思维和 跨职能协作 能力。

我们的工作不再是孤立地完成一个页面,而是为整个组织的开发者提供稳定、高效、易用的“开发工具”——组件库。这意味着我们需要对代码质量、API 设计、性能优化和测试覆盖率有更高的要求。同时,我们必须成为设计师最紧密的技术伙伴,将技术的可行性和工程的约束性前置到设计阶段。

设计系统协作
新组件需求评审会
设计师

根据用户研究,我们需要一个新的“步骤条(Stepper)”组件,用于展示多步流程。这是 Figma 中的高保真设计稿,定义了激活、完成和禁用三种状态。

设计稿已收到。关于状态的颜色,我会确保它们精确映射到设计令牌中的 color.interactive.active, color.status.successcolor.interactive.disabled。此外,考虑到可访问性,我会为每个步骤添加 aria-current 等 ARIA 属性,以便屏幕阅读器能够识别当前步骤。

设计师

非常好。交互方面,我们希望点击已完成的步骤可以返回到该步骤。

这个交互逻辑在技术上是可实现的。但我建议将此功能设计为可选的 Prop,例如 isStepClickable。因为在某些线性且不可逆的流程(如支付)中,我们不希望用户能随意跳转。这种 API 设计能让组件适应更多业务场景。

设计师

这个建议很有价值,我们补充到设计规范里。那么,你预估的开发周期是多久?

在 Storybook 中搭建基础版本并完成单元测试需要 2 天。之后我们可以在 StorybooK 环境中进行交互评审,确认无误后再补充集成测试和文档,最终发布到 NPM,总计大约需要 4 天。

以上对话清晰地展示了开发者在设计系统中的核心职责:我们不仅是实现者,更是 技术顾问,负责提供专业的技术可行性评估;是 API 设计师,负责定义组件的编程接口;也是 质量保证者,通过完备的测试和文档,确保交付产物的可靠性。我们深度参与并影响着设计系统的每一个环节,是确保其工程卓越性的关键角色。


1.1.3 本节小结

设计系统是一个由设计语言、组件库、模式与文档三大支柱构成的综合性工程解决方案,其核心目标是通过系统化的方法,解决规模化数字产品开发中的一致性、效率和质量问题。它要求开发者从传统的“功能实现者”转变为系统的“共建者”,深度参与规范制定、技术实现和基础设施维护,以产品化的思维打造高质量的工程资产。


1.2. 技术选型:为什么说 Radix + Tailwind v4 + daisyUI 是现代化的最佳实践?

技术选型并非简单地选择“流行”的工具,而是基于我们第一节所定义的目标——构建一个高度可定制、体验一致、易于维护的设计系统——所做出的审慎的、体系化的决策。我们将要采纳的 Radix + Tailwind v4 + daisyUI 技术栈,代表了一种与传统组件库截然不同的、兼顾了底层灵活性与上层开发效率的先进设计哲学。理解这种哲学上的差异,是理解我们整个课程架构的关键。

1.2.1. 对比分析:与 Ant Design, Material-UI 等传统组件库在设计哲学上的核心差异

业界存在大量成熟的组件库,如 Ant Design (AntD) 和 Material-UI (MUI)。这些库的共同特点是“开箱即用”,它们提供了一整套包含完整样式和交互逻辑的 UI 解决方案。这种模式被称为“All-in-One”或“Opinionated (强观点型)”方案。

  • 传统方案的优势: 对于快速构建后台管理系统、内部工具或完全接纳其设计语言的项目而言,这类组件库的效率极高。它们预设了大量的设计细节,开发者无需过多关注 UI,即可快速搭建功能。

  • 传统方案的局限性: 其核心问题在于 样式与逻辑的强耦合。当产品的品牌视觉需要高度定制化,与这些库的默认风格存在较大差异时,开发者便会陷入困境。为了覆盖原有样式,我们不得不编写大量“覆盖式”的 CSS,这通常意味着与复杂的 CSS 选择器权重作斗争,甚至滥用 !important,最终导致样式代码难以维护。主题定制功能虽然提供了一定灵活性,但其能力边界有限,无法满足深度的结构性或视觉性定制需求。

我们的目标是构建一个服务于特定品牌、具有独特视觉标识的设计系统,因此,样式的高度可控性是我们的首要考量。这就引出了我们所选择的“Unstyled (无样式)”或“Headless (无头)”范式。这种范式的核心思想,是将组件的 行为逻辑视觉表现 彻底分离。

1.2.2. 深入 Radix UI:理解其“无头”特性如何提供极致的灵活性和一流的可访问性

Radix UI 是“无头”组件理念的最佳实践者之一。它并非一个传统意义上的组件库,而是一个专注于提供 行为、交互和可访问性 的底层 UI 原语的集合。

  • 完全的样式控制权: Radix 组件在默认情况下不携带任何 CSS 样式。这意味着,一个 <Dialog.Trigger> 组件在渲染后,除了必要的交互行为外,其视觉表现与一个普通的 <button> 几乎无异。这赋予了我们 100%的样式控制权,我们可以使用任何偏好的样式方案来为其赋予任意的视觉外观,从根本上杜绝了“样式覆盖”的问题。

  • 内建的、专业的无障碍可访问性 (a11y): 这是 Radix 最具价值的部分。构建一个真正具备完全可访问性的复杂组件(如下拉菜单、对话框)是一项极其复杂的工作,需要深入理解 WAI-ARIA 规范,处理键盘导航、焦点管理、状态宣告等诸多细节。Radix 为我们处理了所有这些复杂性。我们只需要组合其提供的原语,即可自动获得符合专业标准的、健壮的无障碍体验。

  • 预置的复杂交互逻辑: 对于 DropdownMenuCombobox 这类组件,Radix 已经内置了所有必需的状态管理逻辑(例如,菜单的展开/关闭状态)和交互逻辑(例如,点击外部区域自动关闭菜单)。

综上,我们选择 Radix UI 作为我们处理复杂组件 行为逻辑层 的武器。它为我们解决了构建组件过程中最困难的部分(行为逻辑与可访问性),同时将视觉样式完全交由我们掌控。

1.2.3. 拥抱 Tailwind CSS v4:探讨其作为设计系统基石的革命性优势

在拥有了处理行为逻辑的方案后,我们需要一套高效、可约束的样式方案。Tailwind CSS v4 带来了根本性的革新,使其成为与“无头”组件和设计系统理念完美结合的 样式基石

  • CSS 优先的配置与强制的设计约束: v4 版本最核心的变化,是用 CSS 原生的 @theme 指令取代了 tailwind.config.js 作为主题配置的主要方式。这个配置文件正是我们设计令牌 (Design Tokens) 的直接载体。开发者在编码时,只能从这些预设的、源于设计令牌的工具类中进行选择,从而在工具层面强制保证了视觉规范的统一实施。

    1
    2
    3
    4
    5
    6
    7
    /* app.css */
    @import "tailwindcss";

    @theme {
    --color-brand-500: oklch(0.637 0.237 25.331);
    --breakpoint-lg: 64rem;
    }
  • 革命性的性能: v4 采用全新的高性能引擎,能将绝大部分项目的编译时间压缩至 100ms 以内。这种近乎实时的反馈速度,极大地提升了开发体验。

  • 杜绝命名冲突与样式冗余: 通过组合原子化的工具类来构建样式,我们不再需要为各种小型组件编写 BEM 风格的类名,从而避免了命名困难和潜在的全局命名冲突。

  • 极致的性能优化: Tailwind 能在生产环境构建时,通过扫描所有源码文件,精确地移除所有未被使用的 CSS 工具类,确保最终产出的 CSS 文件体积达到最小化。

1.2.4. 效率加速器:引入 daisyUI 简化组件样式

虽然 Tailwind 提供了极致的底层灵活性,但从零开始组合原子类来构建每一个组件依然是繁琐的。为了教学的简便性和开发的效率,我们引入 daisyUI 作为 Tailwind 之上的 组件样式封装层

daisyUI 是一个基于 Tailwind CSS 的插件,它并不引入新的技术,而是提供了一套语义化的、预先设计好的组件类名。

  • 从原子到组件: daisyUI 将常见的 Tailwind 原子类组合封装成了高级组件类。开发者可以直接使用 <button class="btn btn-primary"> 来代替冗长的原子类列表,大幅提升了开发速度。

  • 深度主题化: 它内置了数十种主题,并提供了 primary, secondary, accent 等语义化的颜色名称。这些颜色与 Tailwind v4 的主题系统无缝集成,可以轻松实现一键换肤。

  • 无缝的定制能力: 最关键的是,daisyUI 从未剥夺我们使用 Tailwind 的能力。如果一个 daisyUI 组件不完全符合我们的需求,我们随时可以追加 Tailwind 的原子类来进行微调和覆盖,例如 <button class="btn btn-primary rounded-full">。这种“随时可穿透”的特性,是它优于传统组件库的核心优势。

1.2.5. 认识 cva:为自定义组件提供变体管理能力

尽管 daisyUI 提供了丰富的组件,但在构建一个完整的设计系统时,我们必然会遇到需要创建 daisyUI 未涵盖的、具有高度业务特性的自定义组件的场景。这时,我们就需要一个工具来优雅地管理这些自定义组件的多种变体,而这正是 cva (Class Variance Authority) 的用武之地。

cva 是一个微型工具库,它允许我们以一种清晰、可读的方式,将组件的 Props 声明式地映射到一组 Tailwind 工具类上。

一个 cva 定义的核心结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { cva } from "class-variance-authority";

export const customAvatarVariants = cva(
"inline-flex items-center justify-center rounded-full",
{
variants: {
size: {
small: "h-8 w-8 text-xs",
large: "h-12 w-12 text-lg",
},
status: {
online: "ring-2 ring-green-400",
offline: "grayscale",
}
},
defaultVariants: {
size: "small",
},
}
);

通过 cva,当我们需要构建自己的 CustomAvatar 组件时,可以轻松地管理其 sizestatus 等变体,而无需编写复杂的条件判断逻辑。

技术栈的完美协同:
我们的技术栈是一个分层的、各司其职的有机整体:

  • Radix: 在需要处理极其复杂的交互和无障碍性时,作为可选的、最底层的 行为引擎
  • Tailwind v4: 作为设计令牌的载体和高性能的 原子化样式基石
  • daisyUI: 作为日常开发中最高效的 组件样式封装层,加速 80%的开发工作。
  • cva: 作为构建自定义组件时,管理样式变体的 可选工具

这个范式将“做什么”(行为)与“长什么样”(表现)分离,同时提供了从高层抽象到低层控制的完整能力,是构建一个既稳固又灵活的现代设计系统的最佳工程路径。


1.2.3 本节小结

我们的技术选型决策是基于“分层抽象”与“关注点分离”的核心原则。我们采用 Radix 作为处理复杂行为和可访问性的底层保障;以革命性的 Tailwind CSS v4 作为高性能的样式基石,并通过其 @theme 机制承载我们的设计令牌;在此之上,引入 daisyUI 作为高效率的组件样式抽象层,以加速日常开发;最后,保留 cva 作为构建自定义组件时优雅管理变体的可选方案。这套现代化的、高度解耦的架构,不仅极大地提升了定制化能力和开发体验,更从根本上保障了设计系统在长期演进中的可维护性和可扩展性。


1.3. 工程化基石:从零搭建 Prorise UI 项目

至此,我们已经完成了所有必要的理论铺垫,明确了设计系统的“是什么”以及技术选型的“为什么”。现在,我们将进入更激动人心的环节:将理论转化为实践。本节内容将是纯粹的、高强度的动手操作,我们将从一个空无一物的文件夹开始,一步步搭建起一个包含了现代化前端框架、设计系统规范注入、以及自动化代码质量保障体系的、生产级别的项目地基。

1.3.1. 项目初始化:选择 Next.js 作为我们的开发框架

在开始之前,我们需要选择一个承载我们设计系统的项目框架。正如我们之前所探讨的,我们的选择是 Next.js。如果您是一位经验丰富的 React 开发者,但对 Next.js 尚不熟悉,请不必有任何顾虑。Next.js 并非一个需要您从头学习的全新框架,而应被看作是 React 的一套官方推荐的、自带完整工程化方案的最佳实践集合。您将无缝地掌握它,并体会到它为我们免去的繁琐配置工作。

现在,让我们打开终端,执行以下命令来创建我们的项目骨架。我们将项目命名为 prorise-ui

1
npx create-next-app@latest prorise-ui

执行此命令后,会启动一个交互式安装向导。为了确保项目具备最佳初始状态,请参考以下指引完成选择。我们在此不使用对话格式,而是采用更正式的配置清单形式进行说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
✔ Would you like to use TypeScript? … Yes
# 理由:TypeScript 的类型安全对于构建一个健壮、可维护的设计系统至关重要。

✔ Would you like to use ESLint? … Yes
# 理由:我们需要 ESLint 来保证整个项目的代码质量和风格一致性。

✔ Would you like to use Tailwind CSS? … Yes
# 理由:这是我们技术栈的核心基石,Next.js 会为我们完成所有初始配置。

✔ Would you like to use `src/` directory? … Yes
# 理由:使用 src 目录是一种主流的、更清晰的项目结构组织方式。

✔ Would you like to use App Router? (recommended) … Yes
# 理由:App Router 是 Next.js 最新的、功能更强大的路由系统。

✔ Would you like to use Turbopack? (recommended) » Yes
# Turbopack 是一个为 JavaScript 和 TypeScript 优化的新型增量打包工具,主要优点是速度极快

✔ Would you like to customize the default import alias (@/*)? … No
# 理由:默认的 @/* 别名是一个清晰且广泛接受的约定,无需修改。

完成以上步骤后,create-next-app 会自动安装所有依赖并生成项目文件。现在,我们已经拥有了一个名为 prorise-ui 的目录,其中包含了一个配置完备、可立即运行的现代化 Next.js 应用程序。

1.3.2. 设计系统注入:集成 shadcn/ui, daisyUI 与主题规范

项目框架已经就绪,我们的下一步工作是将设计规范的“神经系统”植入其中。

第一步:初始化主题规范与核心工具

我们进入刚刚创建的 prorise-ui 项目目录,并执行 shadcn/ui 的初始化命令。此命令是 项目配置工具,它会为我们的项目写入规范,而不会添加任何运行时依赖。

1
2
3
4
5
# 首先进入项目目录
cd prorise-ui

# 执行初始化命令
npx shadcn@latest init

在弹出的交互式引导中,您只需根据上一步 create-next-app 时选择的路径,确认各项配置即可。此命令的核心动作是:

  1. 生成 components.json 配置文件。
  2. 在全局 CSS 文件中注入一套完整的、基于 CSS 变量的主题(设计令牌)体系。
  3. 自动安装 tailwind-merge, clsx 等必要的辅助工具库。

第二步:集成 daisyUI 组件样式层

接下来,我们安装 daisyUI 作为快速开发的高阶组件样式库。

1
pnpm install -D daisyui@latest

第三步:配置全局样式与设计令牌体系

现在,我们将上述所有配置整合到我们的全局样式文件中。这是本节最关键的一步,我们将在这里看到一个完整的设计令牌体系是如何在代码中呈现的。

文件路径: src/app/globals.css

首先,shadcn-ui init 会将此文件内容 完全替换 为他固定的结构。这就是我们设计系统的令牌基础,它以原生的 CSS 变量形式存在,并已内置完整的亮/暗色模式切换能力。

接下来,我们在此文件顶部引入 daisyUI 插件,并在文件末尾添加自定义主题配置,让 daisyUI 完全由我们刚刚建立的设计令牌驱动,这个部分我们可以使用 daisyui 的主题生成器 daisyUI and Tailwind CSS theme generator,我就使用我们博客的配色风格来了,

文件路径: src/app/globals.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@import "tailwindcss";
@plugin "daisyui"; /* <-- 步骤一:引入 daisyUI 插件 */

@plugin "daisyui/theme" {
name: "light";
default: true;
prefersdark: false;
color-scheme: "light";
--color-base-100: oklch(98% 0 0);
--color-base-200: oklch(97% 0 0);
--color-base-300: oklch(92% 0 0);
--color-base-content: oklch(20% 0 0);
--color-primary: oklch(0% 0 0);
--color-primary-content: oklch(100% 0 0);
--color-secondary: oklch(70% 0.213 47.604);
--color-secondary-content: oklch(98% 0.016 73.684);
--color-accent: oklch(62% 0.214 259.815);
--color-accent-content: oklch(97% 0.014 254.604);
--color-neutral: oklch(26% 0 0);
--color-neutral-content: oklch(98% 0 0);
--color-info: oklch(54% 0.245 262.881);
--color-info-content: oklch(97% 0.014 254.604);
--color-success: oklch(62% 0.194 149.214);
--color-success-content: oklch(98% 0.018 155.826);
--color-warning: oklch(64% 0.222 41.116);
--color-warning-content: oklch(98% 0.016 73.684);
--color-error: oklch(57% 0.245 27.325);
--color-error-content: oklch(97% 0.013 17.38);
--radius-selector: 1rem;
--radius-field: 0.5rem;
--radius-box: 0.5rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}


@plugin "daisyui/theme" {
name: "dark";
default: false;
prefersdark: false;
color-scheme: "dark";
--color-base-100: oklch(14% 0.005 285.823);
--color-base-200: oklch(21% 0.006 285.885);
--color-base-300: oklch(27% 0.006 286.033);
--color-base-content: oklch(96% 0.001 286.375);
--color-primary: oklch(44% 0.043 257.281);
--color-primary-content: oklch(98% 0.003 247.858);
--color-secondary: oklch(44% 0.017 285.786);
--color-secondary-content: oklch(98% 0 0);
--color-accent: oklch(55% 0.288 302.321);
--color-accent-content: oklch(97% 0.014 308.299);
--color-neutral: oklch(14% 0.005 285.823);
--color-neutral-content: oklch(98% 0 0);
--color-info: oklch(68% 0.169 237.323);
--color-info-content: oklch(97% 0.013 236.62);
--color-success: oklch(72% 0.219 149.579);
--color-success-content: oklch(98% 0.018 155.826);
--color-warning: oklch(70% 0.213 47.604);
--color-warning-content: oklch(98% 0.016 73.684);
--color-error: oklch(63% 0.237 25.331);
--color-error-content: oklch(97% 0.013 17.38);
--radius-selector: 1rem;
--radius-field: 0.5rem;
--radius-box: 0.5rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}

至此,我们的项目地基不仅结构稳固,其“血液”也已经完全统一。无论是未来使用 daisyUI 的便捷组件,还是我们自己构建的复杂组件,都将共享同一套视觉基因。


1.3.3. 质量保障体系:配置自动化代码规范工作流

一个专业的项目,尤其是一个将作为团队基础的设计系统项目,必须从第一天起就建立一套自动化的机制来保障代码质量和协作规范。这不仅能避免个人习惯差异导致的代码风格混乱,更能从源头杜绝潜在的低级错误,防止技术债的累积。

我们将集成业界主流的工具链,在代码提交阶段设立一道坚固的 “质量关卡”:

  • Prettier: 目前最流行的代码格式化工具,支持多种语言,能够确保团队代码风格的绝对统一。
  • ESLint: JavaScript 生态系统的标准代码检查工具,Next.js 已为我们预配置好了核心规则,我们只需将其与 Prettier 整合即可。
  • Husky: 现代化的 Git Hooks 管理工具,让我们能在 commitpush 等 Git 事件发生时执行自定义脚本。
  • lint-staged: 高效的文件过滤工具,确保我们的检查和格式化操作 仅针对 本次提交所修改的文件,而非整个项目,极大地提升了效率。
  • commitlint: 提交信息规范校验工具,强制所有提交都遵循约定式提交规范(Conventional Commits),为后续的版本管理和变更日志(CHANGELOG)自动化生成奠定基础。

第一步:安装开发依赖

我们首先在项目根目录,将所有需要的工具作为开发依赖项进行安装。注意,这里我们需要同时安装 Prettier 以及它与 ESLint 的集成插件。

1
pnpm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional prettier eslint-config-prettier eslint-plugin-prettier prettier-plugin-tailwindcss

各依赖项的作用说明:

  • prettier: 代码格式化工具
  • eslint-config-prettier: 关闭所有与 Prettier 冲突的 ESLint 规则
  • eslint-plugin-prettier: 将 Prettier 规则集成到 ESLint 中
  • prettier-plugin-tailwindcss: Prettier 的 Tailwind CSS 插件,用于自动排序 Tailwind 类名
  • husky: Git Hooks 管理工具
  • lint-staged: 暂存文件过滤工具
  • @commitlint/cli@commitlint/config-conventional: 提交信息规范工具及其约定式提交预设

第二步:配置 Prettier

Prettier 没有类似 init 的初始化命令,我们需要手动创建配置文件。在项目根目录创建 .prettierrc 文件,并复制以下内容:

文件路径: .prettierrc

1
2
3
4
5
6
7
8
9
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"plugins": ["prettier-plugin-tailwindcss"]
}

配置说明:

  • semi: 语句末尾添加分号
  • trailingComma: 在 ES5 兼容的多行语句中添加尾随逗号
  • singleQuote: 使用单引号而非双引号
  • printWidth: 每行代码最大字符数为 80
  • tabWidth: 缩进使用 2 个空格
  • useTabs: 使用空格而非 Tab 缩进
  • plugins: 启用 Tailwind CSS 类名自动排序插件

同时创建 .prettierignore 文件,告诉 Prettier 哪些文件和目录无需格式化:

文件路径: .prettierignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 依赖
node_modules
.pnp
.pnp.js

# 构建输出
.next
out
dist
build

# 测试覆盖率
coverage

# 其他
.DS_Store
*.pem
.env*.local
.turbo
package-lock.json
pnpm-lock.yaml
yarn.lock

第三步:集成 ESLint 与 Prettier

Next.js 项目已经自带了 ESLint 配置,我们只需将 Prettier 规则整合进去即可。打开 eslint.config.mjs 文件,将其修改为如下内容:

文件路径: eslint.config.mjs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { FlatCompat } from '@eslint/eslintrc';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends('next/core-web-vitals', 'next/typescript', 'prettier'),
{
ignores: [
'node_modules/**',
'.next/**',
'out/**',
'build/**',
'next-env.d.ts',
],
},
{
plugins: {
prettier: (await import('eslint-plugin-prettier')).default,
},
rules: {
'prettier/prettier': 'warn',
},
},
];

export default eslintConfig;

关键改动说明:

  1. compat.extends 中添加了 'prettier',这会引入 eslint-config-prettier 的配置,关闭所有与 Prettier 冲突的 ESLint 规则。
  2. 添加了一个新的配置对象,引入 eslint-plugin-prettier 插件,并将 prettier/prettier 规则设置为 warn 级别,这样格式问题会以警告形式显示,不会中断开发流程。

接下来,在 package.json 中添加几个便捷的脚本命令,方便我们手动触发代码检查和格式化:

文件路径: package.json(部分)

1
2
3
4
5
6
7
8
9
10
11
12
{
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start",
"lint": "eslint",
"lint:fix": "eslint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky"
}
}

新增的脚本说明:

  • lint:fix: 运行 ESLint 并自动修复问题
  • format: 使用 Prettier 格式化所有文件
  • format:check: 检查所有文件是否符合 Prettier 格式规范(不修改文件)

第四步:配置 lint-staged

lint-staged 的核心价值在于精准打击,避免了每次提交都对全量代码进行检查的漫长等待。我们在 package.json 中添加 lint-staged 字段:

文件路径: package.json(部分)

1
2
3
4
5
6
7
8
9
10
11
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,css}": [
"prettier --write"
]
}
}

这份配置的含义是:

  • 对于所有提交的 js, jsx, ts, tsx 文件,先执行 eslint --fix 尝试自动修复代码问题,然后执行 prettier --write 进行格式化。
  • 对于 json, md, css 等文件,仅执行 prettier --write 进行格式化。

第五步:配置 commitlint

commitlint 确保每一条提交信息都清晰、规范,让团队成员和工具都能轻易读懂提交历史。我们将采用包含自定义规则的配置,以建立更明确的团队规范。

在项目根目录创建 commitlint.config.js 文件:

文件路径: commitlint.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/** @type {import('@commitlint/types').UserConfig} */
const configuration = {
extends: ['@commitlint/config-conventional'],
rules: {
// 定义允许的提交类型
'type-enum': [
2,
'always',
[
'feat', // 新功能(feature)
'fix', // 修复 bug
'docs', // 文档更新
'style', // 代码格式调整(不影响代码运行)
'refactor', // 重构(既不是新功能也不是修复 bug)
'perf', // 性能优化
'test', // 添加或修改测试
'build', // 构建系统或外部依赖的变更
'ci', // CI/CD 配置文件的变更
'chore', // 其他不修改 src 或测试文件的变更
'revert', // 回退之前的提交
],
],
// 主题不能为空
'subject-empty': [2, 'never'],
// 主题不能以句号结尾
'subject-full-stop': [2, 'never', '.'],
// 提交信息头部最大长度
'header-max-length': [2, 'always', 100],
},
};

module.exports = configuration;

此配置不仅继承了社区的通用规范,还详细定义了我们项目中允许的 11 种提交类型,确保了提交信息的标准化。

提交信息格式说明

约定式提交的基本格式为:

1
2
3
4
5
<type>: <subject>

[可选的正文]

[可选的脚注]

注意type 和冒号之后必须有一个空格,这是规范的强制要求。

示例:

1
2
3
4
5
6
7
# 正确格式
git commit -m "feat: 添加用户登录功能"
git commit -m "fix: 修复导航栏在移动端的显示问题"
git commit -m "docs: 更新 README 安装说明"

# 错误格式(缺少空格)
git commit -m "feat:添加用户登录功能" # ❌ 会被拦截

第六步:初始化 Husky v9 并创建 Hooks

接下来是最关键的一步:激活 husky 并让它来执行我们上面配置好的工具。

版本重要提示: 我们使用的是 Husky v9。相较于 v8 及更早版本,v9 极大地简化了配置流程。它废弃了 husky addhusky set 命令,转而采用更直观、更符合 Shell 习惯的方式——直接创建和编辑钩子文件。如果您参考旧的教程,可能会遇到命令无法使用的情况。

6.1 初始化 Husky

在项目根目录执行以下命令:

1
npx husky init

这条命令会自动完成三件事:

  1. 创建 .husky/ 目录。
  2. package.json 中添加 "prepare": "husky" 脚本。这个脚本会在 pnpm install 后自动运行,确保团队新成员拉取项目后,Husky 能被自动激活。
  3. .husky/ 目录下创建一个名为 pre-commit 的钩子文件示例。

6.2 创建 Git Hooks

现在,我们需要创建或修改两个核心的钩子文件。

Windows 用户特别注意:以下操作 必须在 Git Bash 中执行,而非 PowerShell 或 CMD。

这是因为 Windows 的 PowerShell 使用 echo 命令创建文件时,默认采用 UTF-16LE 编码,而 Husky 的钩子文件必须使用 UTF-8 编码才能被正确执行。如果您在 PowerShell 中执行了 echo 命令创建钩子文件,在提交时会遇到如下错误:

1
2
.husky/pre-commit: .husky/pre-commit: cannot execute binary file
husky - pre-commit script failed (code 126)

解决方案

  1. 推荐做法:右键项目文件夹,选择 “Git Bash Here”,在打开的 Git Bash 终端中执行下面的命令。
  2. 替代方案:如果您坚持使用 PowerShell,请在 VS Code 中手动创建钩子文件,并确保文件编码为 UTF-8,换行符为 LF。

此问题是 Windows 系统特有的,macOS 和 Linux 用户可以忽略。

Git Bash 中,执行以下命令创建 pre-commit 钩子,让它在提交前执行 lint-staged

1
echo "npx lint-staged" > .husky/pre-commit

然后,创建 commit-msg 钩子,让它在提交时校验提交信息:

1
echo 'npx --no -- commitlint --edit $1' > .husky/commit-msg

执行完毕后,我们的 .husky 目录结构如下:

1
2
3
4
.husky/
├── _/ # Husky 内部使用的辅助文件目录
├── commit-msg # 在提交时,用 commitlint 校验信息
└── pre-commit # 在提交前,用 lint-staged 检查文件

如果您在 VS Code 中打开这两个钩子文件,请确认右下角显示的文件编码为 UTF-8,换行符为 LF。如果不是,请点击右下角的编码或换行符标识进行切换。

第七步:测试自动化工作流

现在,我们的自动化质量保障体系已全部配置完毕。让我们来亲自验证一下它的效果。

测试 1:验证 pre-commit 钩子 (lint-staged)

  1. 打开任意一个 .tsx 文件,故意破坏其代码格式,例如添加多余的空格、使用双引号等。
    1
    2
    3
    4
    5
    6
    7
    8
    // src/app/page.tsx
    export default function Home() {
    return (
    <main>
    <h1 className="text-2xl" >Prorise UI</h1>
    </main>
    )
    }
  2. 将文件添加到暂存区,并尝试提交。
    1
    2
    git add .
    git commit -m "feat: add title to homepage"
  3. 预期结果: 您会看到终端输出了 lint-staged 的运行信息,ESLint 和 Prettier 依次对文件进行了检查和格式化。提交成功后,再次查看刚才的文件,会发现多余的空格已经被自动清除了。如果存在 ESLint 无法自动修复的错误,提交则会被中止,您需要手动修复后才能继续提交。

测试 2:验证 commit-msg 钩子 (commitlint)

  1. 随便修改一个文件,然后尝试使用不规范的提交信息进行提交。
    1
    2
    git add .
    git commit -m "更新了一下页面"
  2. 预期结果: 提交 失败commitlint 会立即报错,提示您 typesubject 存在问题。
    1
    2
    3
    4
    5
    ⧗   input: 更新了一下页面
    ✖ subject may not be empty [subject-empty]
    ✖ type may not be empty [type-empty]

    ✖ found 2 problems, 0 warnings
  3. 使用规范的格式再次提交:
    1
    git commit -m "docs: 更新页面文案"
    提交成功。

测试 3:验证格式错误提示

尝试提交一个缺少空格的提交信息:

1
git commit -m "feat:添加新功能"

预期结果: 提交失败,提示格式错误。正确的格式应为:

1
git commit -m "feat: 添加新功能"

注意冒号后面的空格,这是约定式提交规范的强制要求。

至此,我们的自动化 “防火墙” 已成功建立。从现在开始,每一次 git commit 都会自动触发代码质量检查和格式化,每一条提交信息都必须符合团队规范,确保了代码库的长期健康和可维护性,我们现在正确的将项目提交到我们的 github 仓库

1
2
3
4
5
6
7
git commit -m "chore: 配置代码质量保障工具链

- 添加 Husky 9 用于 Git Hooks 管理
- 添加 lint-staged 用于暂存文件检查
- 添加 commitlint 用于提交信息规范
- 添加 Prettier 用于代码格式化
- 集成 ESLint 与 Prettier"

image-20251011222421520

一个比较有意思的是 next 会将默认的脚手架文件做一个提前标记,以做区分是否改动过原脚手架的内容

第八步:引入 conventional-changelog 实现自动化变更日志

在规范化提交信息之后,我们还可以更进一步,利用这些结构化的提交历史自动生成项目的变更日志(CHANGELOG)。conventional-changelog 正是为此而生的工具,它能够读取符合约定式提交规范的 Git 历史,自动生成格式规范、内容清晰的 CHANGELOG.md 文件。

这不仅为团队成员和用户提供了清晰的版本演变记录,更为后续的版本发布、语义化版本管理(Semantic Versioning)奠定了坚实基础。

1.安装 conventional-changelog-cli

在项目根目录执行以下命令,安装 conventional-changelog-cli 作为开发依赖:

1
pnpm install -D conventional-changelog-cli

2 配置 CHANGELOG 生成脚本

打开 package.json,在 scripts 字段中添加以下命令:

文件路径: package.json(部分)

1
2
3
4
5
6
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md",
}
}

新增脚本的说明:

  • changelog: 手动生成或更新 CHANGELOG.md 文件

    • -p angular: 使用 Angular 提交规范(与我们的 commitlint 配置一致)
    • -i CHANGELOG.md: 将生成的日志写入 CHANGELOG.md 文件
    • -s: 增量模式,只生成自上次发布以来的变更,不会覆盖已有内容
  • version: 与 npm/pnpm version 命令配合使用,在版本升级时自动生成 CHANGELOG

    • 执行完 conventional-changelog 后会自动将 CHANGELOG.md 添加到暂存区
    • 通常在执行 pnpm version patch/minor/major 时会自动触发

3. 生成第一份 CHANGELOG

在首次使用时,我们可以生成包含所有历史版本的完整 CHANGELOG:

1
2
# 生成所有历史版本的 CHANGELOG(仅首次)
npx conventional-changelog -p angular -i CHANGELOG.md -s -r 0

参数说明:

  • -r 0: 生成所有版本的日志(release count = 0 表示全部)

执行后,项目根目录会生成 CHANGELOG.md 文件,内容按照版本和提交类型(feat、fix、perf 等)自动分类整理。

4. 日常使用流程

配置完成后,您的日常版本发布流程可以如下进行,在这之前我们有必要要了解一下版本号的规范:

  • 主版本号 (Major):当你做了不兼容的 API 修改时,需要升级主版本号。这通常意味着软件发生了重大的变化,可能会导致依赖该软件的旧版本代码无法正常工作。例如,从版本 1.7.0 升级到 2.0.0。
  • 次版本号 (Minor):当你以向下兼容的方式添加了新功能时,需要升级次版本号。“向下兼容”意味着新版本虽然增加了功能,但不会破坏依赖旧版本软件的代码。例如,从版本 1.7.0 升级到 1.8.0。
  • 修订号 (Patch):当你做了向下兼容的问题修复时,需要升级修订号。这通常指的是修复了软件中的 bug,而没有引入新功能或不兼容的改动。例如,从版本 1.7.0 升级到 1.7.1。
1
2
3
4
5
6
7
8
9
10
11
12
# 1. 确保所有代码已提交
git add .
git commit -m "feat: 完成某个新功能"

# 2. 使用 pnpm version 升级版本(会自动生成 CHANGELOG)
pnpm version patch # 修订号升级 1.0.0 -> 1.0.1
pnpm version minor # 次版本号升级 1.0.0 -> 1.1.0
pnpm version major # 主版本号升级 1.0.0 -> 2.0.0

# 3. 推送代码和标签
git push
git push --tags

执行 pnpm version 时,会依次触发以下自动化流程:

  1. 更新 package.json 中的版本号
  2. 执行 version 脚本,生成最新的 CHANGELOG
  3. CHANGELOG.md 添加到暂存区
  4. 创建一个 Git commit 和 tag

如果您只想手动生成 CHANGELOG 而不升级版本,可以单独执行:

1
pnpm run changelog

5 CHANGELOG 示例

生成的 CHANGELOG.md 文件格式示例如下:

1
2
3
4
5
6
7
8
9
## [0.1.2](https://github.com/Prorise-cool/Prorise-UI/compare/v0.1.1...v0.1.2) (2025-10-12)

## 0.1.1 (2025-10-12)

### Features

- 完成某个新功能 ([a1bcc83](https://github.com/Prorise-cool/Prorise-UI/commit/a1bcc83c4d11392962cb62c2a24bd2f5aa5df030))

# 0.1.0 (2025-10-12)

可以看到,所有提交按类型自动分类(Features、Bug Fixes、Performance Improvements 等),并且包含了提交信息和 commit hash 链接,方便追溯具体改动。

注意事项

  1. 提交信息必须规范:只有符合约定式提交规范的 commit 才会被收集到 CHANGELOG 中。不规范的提交信息会被忽略。

  2. 常规改动会被包含:默认情况下,featfixperf 类型的提交会出现在 CHANGELOG 中,而 docsstylerefactortestchore 等类型不会出现(但它们仍然是有效的提交类型,只是不会展示给最终用户)。

  3. Breaking Changes 会突出显示:如果提交信息中包含 BREAKING CHANGE: 标识,该提交会在 CHANGELOG 中被特别标注,提醒用户这是一个破坏性变更。

示例:

1
2
3
git commit -m "feat: 重构用户认证模块

BREAKING CHANGE: 用户登录接口的响应格式已改变,需要更新客户端代码"

至此,我们不仅建立了规范化的提交信息体系,还拥有了自动化的变更日志生成能力。项目的每一次演进都将被清晰地记录和呈现。


1.3.4 本节小结

在本节中,我们完成了从零到一的飞跃。我们首先使用 create-next-app 初始化了一个功能完备的现代化 React 项目。紧接着,我们利用 shadcn/ui init 命令,为项目注入了一套专业、基于 CSS 变量的底层设计令牌体系。随后,我们集成了 daisyUI 作为高效率的组件样式层,并通过自定义主题配置,使其完全由我们的设计令牌驱动,实现了视觉规范的顶层统一。

最后,我们配置了一套完整的代码质量保障工具链:Prettier 负责代码格式化,ESLint 负责代码质量检查,两者完美集成;Husky 管理 Git Hooks,lint-staged 确保只检查修改的文件,commitlint 强制提交信息规范化。这套自动化工作流在每次提交时都会自动运行,确保入库的每一行代码都符合最高质量标准。

至此,一个结构清晰、规范统一、质量可靠的专业设计系统项目基座已搭建完毕,为后续的组件开发铺平了道路。