4、开发环境搭建:Storybook配置、文档工具(MDX)集成、热更新调试、Mock服务搭建

好,咱们进入第四讲。说实话,开发环境搭建这件事,很多同学觉得「不就是装个包跑起来吗?」——嗯,我以前也这么想。直到有一次,我在一个组件库项目里折腾了整整两天,就为了把 Storybook 和 TypeScript 的路径别名搞通。从那以后,我养成了一个习惯:先把环境搭稳,再写一行业务代码。

这一章,我会带你一步步搭好四个核心模块:Storybook 作为组件开发与预览的沙箱,MDX 作为文档编写工具,热更新 保证开发效率,以及 Mock 服务 让组件在真实数据下跑通。你想想看,如果连这些基础设施都没弄好,后面写 50 个组件时得多痛苦?

4.1 Storybook 配置:从零到可运行

我个人习惯用 Storybook 7.x 版本,它比 6.x 快了不少,而且对 Vite 的支持更原生。咱们的项目是基于 Vite 的,所以直接装 @storybook/react-vite 这个包。

# 在项目根目录执行
npx storybook@latest init --type react-vite

这条命令会自动检测你的 package.json,安装依赖,并生成 .storybook 目录。里面有两个核心文件:main.jspreview.js

main.js 负责配置 Storybook 的入口、插件和构建行为。我一般会这样改:

// .storybook/main.js
export default {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
    '@storybook/addon-a11y', // 可访问性检查,我强烈建议加上
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
  docs: {
    autodocs: 'tag', // 自动生成文档,后面会用到
  },
};
小提示: 如果你用了路径别名(比如 @/ 指向 src/),记得在 vite.config.ts 里配好,然后在 .storybook/main.js 里加上 viteFinal 钩子来合并配置。我曾经因为忘了这一步,导致所有 import 都报错,排查了半小时。

4.2 文档工具集成:用 MDX 写组件文档

MDX 是什么?说白了就是 Markdown 里能写 JSX。你可以在文档里直接插入组件实例,还能实时交互。这对组件库来说太香了——文档即演示,演示即文档。

Storybook 7 对 MDX 的支持已经非常成熟。你只需要在 stories 目录下创建一个 .stories.mdx 文件,就能开始写文档了。

// Button.stories.mdx
import { Meta, Story, Canvas } from '@storybook/addon-docs';
import { Button } from './Button';

<Meta title="通用/Button" component={Button} />

# Button 按钮

这是一个基础按钮组件,支持 `primary`、`default`、`dashed` 三种类型。

## 基础用法

<Canvas>
  <Story name="Primary">
    <Button type="primary">主要按钮</Button>
  </Story>
  <Story name="Default">
    <Button>默认按钮</Button>
  </Story>
</Canvas>

## 属性说明

| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| type | 'primary' \| 'default' \| 'dashed' | 'default' | 按钮类型 |
| size | 'small' \| 'medium' \| 'large' | 'medium' | 按钮尺寸 |
| disabled | boolean | false | 是否禁用 |

这里要注意:MDX 文件里的 <Story> 标签,每个 name 必须唯一。我刚开始写的时候,复制粘贴忘了改 name,结果 Storybook 直接报「duplicate story id」——嗯,这种低级错误犯过一次就不会再犯了。

核心思路: MDX 文档 = 组件演示 + 使用说明 + API 表格。三者缺一不可。你想想看,如果用户只看代码不看文档,那组件库的价值就大打折扣了。

4.3 热更新调试:让开发飞起来

热更新(HMR)是开发体验的底线。没有热更新的组件库开发,就像没有刹车的赛车——能跑,但随时可能翻车。

Storybook 7 默认集成了 Vite 的 HMR,理论上你改完代码,浏览器会自动刷新。但实际项目中,我遇到过几次「改了代码但页面没反应」的情况。排查下来,大多是这两个原因:

  • 文件路径大小写不一致:Windows 不区分大小写,但 Linux 区分。如果你在 Windows 上开发,部署到 Linux 上就可能找不到文件。我建议统一用小写加连字符命名,比如 my-button.tsx
  • 循环依赖:组件 A 引用了 B,B 又引用了 A。Storybook 的 HMR 遇到循环依赖会直接挂掉。解决办法是用 lazy 加载或重构依赖关系。

另外,我习惯在 .storybook/preview.js 里加一段全局配置,让热更新更稳定:

// .storybook/preview.js
export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  // 强制重新挂载组件,避免状态残留
  layout: 'centered',
  // 热更新时保留滚动位置
  options: {
    storySort: {
      order: ['通用', '布局', '导航'],
    },
  },
};
注意: 如果你用了 CSS-in-JS 方案(比如 styled-components),热更新时样式可能会闪烁。解决办法是在 preview.js 里添加一个全局样式重置,或者用 @storybook/addon-styling 插件来管理样式注入时机。

4.4 Mock 服务搭建:让组件有数据可用

组件开发最怕什么?怕后端接口还没写好,你只能写死数据。写死数据又导致组件没法测试边界情况。所以,Mock 服务是组件库开发的标配。

我个人推荐用 MSW(Mock Service Worker)。它拦截网络请求,返回你定义的数据,而且不污染全局环境。更妙的是,它可以在 Storybook 里直接使用。

安装 MSW:

npm install msw --save-dev

然后在 .storybook/preview.js 里初始化:

// .storybook/preview.js
import { initialize, mswDecorator } from 'msw-storybook-addon';

// 初始化 MSW
initialize();

// 全局添加 MSW 装饰器
export const decorators = [mswDecorator];

接下来,在具体的 stories 文件里,你可以为每个组件定义不同的 Mock 数据:

// UserList.stories.js
import { UserList } from './UserList';

export default {
  title: '数据展示/UserList',
  component: UserList,
};

// 正常数据
export const WithData = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users', () => {
          return HttpResponse.json([
            { id: 1, name: '张三', email: 'zhangsan@example.com' },
            { id: 2, name: '李四', email: 'lisi@example.com' },
          ]);
        }),
      ],
    },
  },
};

// 空数据
export const Empty = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users', () => {
          return HttpResponse.json([]);
        }),
      ],
    },
  },
};

// 错误状态
export const Error = {
  parameters: {
    msw: {
      handlers: [
        http.get('/api/users', () => {
          return new HttpResponse(null, { status: 500 });
        }),
      ],
    },
  },
};

你看,这样每个 story 都可以独立控制接口返回的数据。我在项目中遇到过最头疼的情况是:组件在正常数据下跑得好好的,一遇到空数组就崩溃。有了 Mock,你可以在开发阶段就覆盖这些边界情况。

避坑指南: 我曾经在 Mock 服务里忘了配置 Content-Type 头,导致组件解析 JSON 失败。排查了半天才发现是 Mock 返回的响应头不对。所以,写 Mock 时一定要模拟真实的 HTTP 响应结构,包括状态码、响应头、响应体。

4.5 整合验证:跑通一个完整流程

环境搭好了,咱们来验证一下。写一个最简单的 Button 组件,然后启动 Storybook:

npm run storybook

你应该能看到:

  • Storybook 界面正常加载,左侧有「通用/Button」目录
  • 点击 Button 组件,能看到 MDX 文档和交互演示
  • 修改 Button 的代码,页面自动热更新
  • 如果组件有网络请求,Mock 服务能正确拦截并返回数据

如果哪个环节卡住了,别慌。先看控制台报错,再检查配置文件。我个人的排查顺序是:依赖版本 → 配置文件 → 代码语法。90% 的问题都出在前两步。

好,这一章的内容就到这里。下一章咱们会正式进入组件开发,从设计规范开始,一步步构建一个完整的 Button 组件。到时候,你会发现今天搭的环境有多重要——没有这些基础设施,后面的开发就是空中楼阁。