Storybook 第12周笔记-组件拆分 + Storybook 入门(组件库的代码版)
路线图阶段:第三阶段(Design Engineer 工具箱)第12周 —— Storybook 入门 任务:把配色生成器里的组件抽独立、装 Storybook、给四个组件写 story 核心收获:Storybook = 你 Figma 组件库的代码版;以及一个元技能 —— 学新工具时分清”必须懂的概念”和”照抄即可的样板”
0. 心智模型:Storybook 是”Figma 组件库的代码版”
你在 Figma 里维护组件库时早就在做这件事:
- 一个单独的 “Components” 页面,和产品设计稿分开。
- 每个组件摆出各种状态/变体(Button: default / hover / disabled)。
- 别人看这页就知道库里有什么、每个长什么样、怎么用。
Storybook 就是这件事的代码版:独立网页(和 app 分开跑),把每个组件隔离展示,每个组件可以有多个 story(= 不同 props 下的样子)。
Figma "Components" 页面 ↔ Storybook
Button / default Button / Default
Button / hover ↔ Button / Hover
Button / disabled Button / Disabled
一个 story(故事) = 一个组件在一组特定 props 下的样子。 Swatch 有 “Light” / “Dark” 两个 story 并排对比 —— 就是 Figma 里摆变体。
为什么 design engineer 特别需要它
- 隔离开发:改一个组件不用跑整个 app,单独拎出来喂死数据看效果。
- 文档化:组件库的”说明书”,作品集里证明”我会像团队那样维护+记录组件”。
- 状态枚举:逼你想清楚组件有哪些状态 —— 本身就是设计系统思维。
1. 进 Storybook 前的准备:组件得先”独立”
Storybook 展示的是独立、props 驱动的组件。所以装之前要先做两件事:
(a) 把埋在 .map() 里的 JSX 抽成独立组件(week5 技能兑现)
色卡原本是写死在 ColorPalette 里的一段 .map(),不是组件 → 先抽成 <Swatch />。
抽组件三步(week5):
- 列出它渲染自己需要哪些数据 → 这些就是 props。
- 定义 props interface(照
ColorPickerProps套路)。 - 共享 state(如
copied)放最近共同父组件,不跟着子组件走 —— 父组件算好布尔isCopied={copied === role}再传下去。
interface SwatchProps {
role: string;
color: HSL; // ⚠️ 不是 string!color 实际是 {h,s,l} 对象(week10 名实相符教训)
isCopied: boolean; // 父组件算好的布尔,不传整个 copied
onCopy: (role: string, hex: string) => void; // 回调命名惯例:onXxx 不是 handleXxx
}
踩过的坑:
onClick={() => onCopy(role, hslToHex(color))}—— 箭头包了,但要真的调用 + 传参;{ onCopy }只是写了个名字啥也没干(口诀:事件处理要传参,箭头包裹慢慢谈)。key加在被 .map() 直接生成的元素上(父组件的<Swatch key={role}>),不在组件内部。
(b) 仓库卫生:作品集项目独立成单独 repo(week6 原则兑现)
- 嵌套仓库(repo 套 repo)会给 git/部署挖坑 → 配色生成器移出学习仓库,独立平级目录。
git rev-parse --show-toplevel看当前仓库根在哪;移动前先 commit + push。- 面试官扫一眼 GitHub 就能看出项目结构专不专业 —— 这是 design engineer 基本功。
2. 装 Storybook
# 在项目根目录(和 package.json 同级)
npx storybook@latest init
- 自动检测 Vite + React + TS,自动配置,装依赖。
- 生成
.storybook/(配置)和src/stories/(官方示例,认识格式后整个可删)。 - 装完自动跑
localhost:6006。
那个”Tailwind 全丢”的坑(本次没踩到,但要知道)
Storybook 是独立环境,默认不加载项目的 Tailwind → 组件可能样式全丢。
- 解法:
.storybook/preview.ts里import "../src/index.css"(引入 Tailwind 的那个文件)。 - ⚠️ 本次 Storybook 10.4 + Vite 自动接上了 Tailwind,没踩到。但换配置(老 webpack 项目)还可能撞上 —— 记住坑在哪。
3. story 文件结构:两层(关键认知)
story 文件分两层,学习态度要分开对待:
┌──────────────────────────────────────┐
│ 样板层(boilerplate)= 照抄即可 │ 认识每行干嘛就够,不深究
│ - import type { Meta, StoryObj } │
│ - const meta: Meta<typeof X> = {...} │
│ - export default meta │
│ - type Story = StoryObj<typeof X> │
├──────────────────────────────────────┤
│ 内容层 = 必须懂 │ 这是 story 的灵魂
│ - args: { ... } ← 喂给组件的 props │
│ - 每个 export const = 一个状态 │
└──────────────────────────────────────┘
样板层:一句话档案(存着,别钻)
| 样板行 | 作用 | 深究? |
|---|---|---|
import type { Meta, StoryObj } | 导入两个类型工具(import type = 只导类型不导值) | 否 |
const meta: Meta<typeof X> = {...} | 文件总配置(展示哪个组件、标题叫啥) | 否 |
export default meta | Storybook 规定 meta 必须默认导出 | 否(规定而已) |
type Story = StoryObj<typeof X> | 给”单个 story 类型”起短名(type = 类型别名) | 否 |
内容层:必须懂
export const LightSwatch: Story = {
args: { // ← args 就是 props!
role: "background",
color: { h: 30, s: 20, l: 95 },
isCopied: false,
onCopy: () => {}, // ← mock 空函数:隔离环境没真父组件,占位
},
};
args= props。你在 Figma 给组件实例填属性面板,这里就是填 args。一模一样。- 每个
export const= 一个 story = 一个状态。组件有几个值得展示的状态,就写几个。- Swatch 写两个(Light/Dark),验证
textColorFor在亮暗下都对。 - ColorPicker/ColorInput 只有一个基础态 → 一个
Default就够。
- Swatch 写两个(Light/Dark),验证
- 命名:多状态按状态命名(LightSwatch/DarkSwatch);单状态叫
Default。
4. 白赚的奖励:自动 Controls 面板
写完 story,Storybook 自动从 props 类型生成一个可交互控制面板 —— 能实时改 role、调 color 的 h/s/l、点 isCopied 的 True/False,组件实时变。
这就是 Figma 组件的属性面板,自动生成的代码版。 你 week9 老实写的那个 interface(
SwatchProps),现在反过来奖励你一个交互式调试台。类型写得好,工具回馈你。
5. 逻辑层红利:story 里直接复用纯函数
PreviewCard 的 prop 是一整套 palette,手敲五个 HSL 很烦。但项目里本来就有 generatePalette:
import { PreviewCard, generatePalette, hexToHsl } from "./App";
export const Default: Story = {
args: { palette: generatePalette(hexToHsl("#C16952")) }, // 复用逻辑层,不手敲
};
这是 week6 “逻辑/UI 分离”架构的又一次回报:纯函数不依赖 React,在 Storybook 里也能直接拿来用。
6. ⭐ 本周两个语法认知(反复栽过,这次彻底拿下)
(a) = vs : 一劳永逸判断法
== “造一个新名字(变量/类型别名)并赋值”。:= “在已有结构内部贴标签(贴类型 / 对象 key 配 value)”。
const meta : Meta<typeof X> = {
// ↑①贴类型标签 ↑②造名字+赋值(=)
component : PreviewCard,
// ↑③对象内部 key 配 value(:)
}
type Story = StoryObj<typeof X>; // 造新类型别名 → =
速记:只有”造新名字”用 =;贴类型、对象里配值,全是 :。看到 {} 里面,几乎一定是冒号。
之前的错:args = {...} —— args 在对象内部,是配 value 的地方,必须 :。
(b) export 判断法(值和类型同一套规则)
别的文件要写出这个名字 → 要 import → 源头要 export;没写出 → 不用管。
- story 里写了
generatePalette(...)→App.tsx里generatePalette要 export。 - story 里没写
HSL/Palette这两个名字(靠函数推断类型)→ 这两个 interface 不用 export。 Palette内部引用HSL是 App.tsx 内部的事,不影响 export 判断(export 只跟”别的文件要不要用”有关)。
7. ⭐⭐ 元技能:学新工具时,分清”概念”和”样板”
本周最值钱的领悟(不止 Storybook,学任何新框架都用):
- 概念(为什么用、何时用、解决什么)→ 必须懂。 例:为什么隔离展示组件?args 是什么?组件有几个状态?
- 样板/语法仪式(框架要求的固定写法)→ 认识即可,照抄不丢人。
例:
export default meta、Meta<typeof X>泛型内部如何运作。
会用 ≠ 懂实现。开车不用懂发动机。
Meta<typeof X>内部怎么推断 args 是库作者该懂的,不是使用者。健康 vs 危险:
- 健康(你现在):懂内容(args=props、状态枚举),对样板没把握 → 没漏洞,放心走。
- 危险:能默写样板,但不懂 args 是 props → 教程地狱,抄得动手不会迁移。
对应路线图原则”概念先行,再看语法”:概念你已通,样板本来就该抄 —— 不是理解漏洞。
第12周验收
- 理解 Storybook = Figma 组件库的代码版(隔离展示 + 多状态 story)
- 把 Swatch 从 .map() 抽成独立组件(props 驱动,key/回调/状态提升都对)
- 配色生成器独立成单独 repo(仓库卫生)
- 装好 Storybook,四个组件全进库
- 会写 story:Meta / StoryObj / args 结构,懂 args = props
- 懂”每个 export = 一个状态”,会按状态命名
- 白赚自动 Controls 面板,理解它来自 props 类型
- ⭐ 拿下
=vs:判断法、export 判断法 - ⭐⭐ 元技能:分清”必须懂的概念”和”照抄即可的样板”
反复提醒过、需继续巩固的点
- 事件回调:箭头包裹后要真调用+传参
() => onCopy(role, hslToHex(color)),不是只写名字。 - 类型要”名实相符”:color 是 HSL 不是 string(week10 教训延续)。
- 组件定义在顶层,不嵌套在另一个组件函数内部(week5 铁律)。
下一步(第13周预告)—— 仍在第三阶段
- 正经重构:颜色纯函数抽到
colors.ts、每个组件拆独立文件(Swatch.tsx / PreviewCard.tsx…)。 现在四个组件还挤在 App.tsx 里靠 export 凑合 → 重构成”一个组件一个文件”才是组件库该有的样子。 - 之后:design tokens 系统化、基础无障碍(a11y)、Framer Motion 动画。
- 阶段大作业:小而精致的组件库(按钮/输入框/卡片/弹窗)。
- ⚠️ 重构是个需要专注的独立任务,涉及移动一堆函数 + 改一堆 import,单独做、别和别的混。一次一件事。