Storybook 第12周笔记-组件拆分 + Storybook 入门(组件库的代码版)

路线图阶段:第三阶段(Design Engineer 工具箱)第12周 —— Storybook 入门 任务:把配色生成器里的组件抽独立、装 Storybook、给四个组件写 story 核心收获:Storybook = 你 Figma 组件库的代码版;以及一个元技能 —— 学新工具时分清”必须懂的概念”和”照抄即可的样板”


0. 心智模型:Storybook 是”Figma 组件库的代码版”

你在 Figma 里维护组件库时早就在做这件事:

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 特别需要它

  1. 隔离开发:改一个组件不用跑整个 app,单独拎出来喂死数据看效果。
  2. 文档化:组件库的”说明书”,作品集里证明”我会像团队那样维护+记录组件”。
  3. 状态枚举:逼你想清楚组件有哪些状态 —— 本身就是设计系统思维。

1. 进 Storybook 前的准备:组件得先”独立”

Storybook 展示的是独立、props 驱动的组件。所以装之前要先做两件事:

(a) 把埋在 .map() 里的 JSX 抽成独立组件(week5 技能兑现)

色卡原本是写死在 ColorPalette 里的一段 .map(),不是组件 → 先抽成 <Swatch />

抽组件三步(week5):

  1. 列出它渲染自己需要哪些数据 → 这些就是 props。
  2. 定义 props interface(照 ColorPickerProps 套路)。
  3. 共享 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
}

踩过的坑:

(b) 仓库卫生:作品集项目独立成单独 repo(week6 原则兑现)


2. 装 Storybook

# 在项目根目录(和 package.json 同级)
npx storybook@latest init

那个”Tailwind 全丢”的坑(本次没踩到,但要知道)

Storybook 是独立环境,默认不加载项目的 Tailwind → 组件可能样式全丢。


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 metaStorybook 规定 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 空函数:隔离环境没真父组件,占位
  },
};

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;没写出 → 不用管。


7. ⭐⭐ 元技能:学新工具时,分清”概念”和”样板”

本周最值钱的领悟(不止 Storybook,学任何新框架都用):

会用 ≠ 懂实现。开车不用懂发动机。Meta<typeof X> 内部怎么推断 args 是库作者该懂的,不是使用者。

健康 vs 危险:

  • 健康(你现在):懂内容(args=props、状态枚举),对样板没把握 → 没漏洞,放心走。
  • 危险:能默写样板,但不懂 args 是 props → 教程地狱,抄得动手不会迁移。

对应路线图原则”概念先行,再看语法”:概念你已通,样板本来就该抄 —— 不是理解漏洞。


第12周验收


反复提醒过、需继续巩固的点


下一步(第13周预告)—— 仍在第三阶段