首页
/ 在 Storybook 中初始化 MSW Addon:通过 mswLoader 与 definePreview 为全部 Story 提供接口 Mock 能力

在 Storybook 中初始化 MSW Addon:通过 mswLoader 与 definePreview 为全部 Story 提供接口 Mock 能力

2026-09-07 13:41:06作者:龚格成

本篇指南讲解如何在 Storybook 项目中初始化 msw-storybook-addon,让基于 Mock Service Worker(MSW)的网络请求拦截能力覆盖到全部 Story。你将掌握两种注册方式——CSF 3 下在 .storybook/preview 中挂载项目级 mswLoader(),以及实验性 CSF Next 下通过 definePreviewaddons 数组接入 addonMsw(),并同步了解配套的安装、Service Worker 生成、静态资源目录配置以及迁移到 addon v3 的注意点。文中的多框架代码片段来自 msw-addon-initialize.md,完整上下文可见 mocking-network-requests.mdx

为什么需要在 preview 中完成初始化

MSW 依靠 Service Worker 在浏览器网络层拦截请求并返回 Mock 数据。要让这种拦截能力渗透进 Storybook 的渲染环境,msw-storybook-addon 必须把"启动 / 重置 Mock Server"的逻辑接入到 Storybook 的运行周期中。

其接入点是 Loader 机制。在 Storybook 中,Loader 是在 Story 及其装饰器渲染之前异步执行的数据加载函数,执行结果会通过 render 上下文注入到 Story;其中项目级(global)Loader 通过 .storybook/preview.jsloaders 导出对所有 Story 生效(参见 loaders.mdx)。mswLoader() 正是一个为此设计的项目级 Loader:它在每条 Story 渲染前启动 MSW,并在 Story 切换后清理 Handler,从而保证"每条 Story 从干净状态开始,只有自己注册的接口 Mock 生效"。

注意,本文与所有配套片段面向 MSW addon v3。若你仍在使用 v2,需要先完成版本迁移,官方提供了自动迁移 Codemod:

npx msw-storybook-migrate

该命令可自动把 v2 的配置与 Story 迁移到 v3 语法,迁移指南与本节警告完整收录于 mocking-network-requests.mdx 的页面开头。

方式一:CSF 3 下注册项目级 mswLoader

在传统 CSF 3 写法中,注册动作发生在 .storybook/preview.js / .storybook/preview.ts。核心是从 msw-storybook-addon/csf3 子路径导入 mswLoader,并把调用结果 mswLoader() 放进 loaders 数组。这样一个数组会作为项目级 Loader 应用到项目中所有 Story。

以通用渲染器(React、Vue 等各类框架)的 .storybook/preview.js 为例:

import { mswLoader } from 'msw-storybook-addon/csf3';

export default {
  /*
   * Register the MSW loader for all stories
   * See https://github.com/mswjs/msw-storybook-addon#csf-30
   * to learn how to customize it
   */
  loaders: [mswLoader()],
};

若项目使用 TypeScript,则把 .storybook/preview.ts 写成带类型标注的 Preview 对象。注释中需要把 @storybook/your-framework 替换为实际框架包名(如 @storybook/react-vite@storybook/nextjs@storybook/vue3-vite 等):

import type { Preview } from '@storybook/your-framework';

import { mswLoader } from 'msw-storybook-addon/csf3';

const preview: Preview = {
  /*
   * Register the MSW loader for all stories
   * See https://github.com/mswjs/msw-storybook-addon#csf-30
   * to learn how to customize it
   */
  loaders: [mswLoader()],
};

export default preview;

参数说明与自定义入口

mswLoader() 作为函数调用,其设计目的与文档注释中提示的"learn how to customize it"一致——它支持按需传入初始化选项(例如自定义 onUnhandledRequest 策略、handlers 预置列表等),用于调整 MSW 在当前 Story 渲染前的启动行为。当你不需要任何自定义时,直接使用无参数形式 mswLoader() 即可得到开箱即用的默认配置。

代码中 loaders 是数组而非单个函数,这一点值得展开:Storybook 允许多个项目级 Loader 并行执行、结果合并进 Story 上下文的 loaded 字段,若键冲突则"后定义的优先"。因此即便将来你还要挂载其他数据加载逻辑,也只需在数组中追加元素,而不会被 mswLoader() 覆盖(loaders.mdx)。

方式二:CSF Next 实验性写法接入 addon

对于使用实验性 CSF Next 语法的项目,注册方式发生变化:不再使用 loaders 数组,而是把 msw-storybook-addon 主入口默认导出的 addonMsw() 注册进 definePreviewaddons 数组。definePreview 需要从当前框架包导入(React 系为 @storybook/react-vite@storybook/nextjs@storybook/nextjs-vite,Vue 为 @storybook/vue3-vite,Angular 为 @storybook/angular,Web Components 为 @storybook/web-components-vite)。

React(.storybook/preview.tsx,TypeScript 或 JavaScript 均可):

import { definePreview } from '@storybook/react-vite';

import addonMsw from 'msw-storybook-addon';

export default definePreview({
  /*
   * Register the MSW loader for all stories
   * See https://github.com/mswjs/msw-storybook-addon#csf-next
   * to learn how to customize it
   */
  addons: [addonMsw()],
});

Vue3(.storybook/preview.ts):

import { definePreview } from '@storybook/vue3-vite';

import addonMsw from 'msw-storybook-addon';

export default definePreview({
  addons: [addonMsw()],
});

Angular(.storybook/preview.ts):

import { definePreview } from '@storybook/angular';

import addonMsw from 'msw-storybook-addon';

export default definePreview({
  addons: [addonMsw()],
});

Web Components(.storybook/preview.ts):

import { definePreview } from '@storybook/web-components-vite';

import addonMsw from 'msw-storybook-addon';

export default definePreview({
  addons: [addonMsw()],
});

在上述各框架下,JS 变体仅把类型导入换成 JS 运行时导入、文件扩展名从 .ts/.tsx 调整为 .js,其余结构完全一致(React 的 JS 变体使用 @storybook/react-vitedefinePreview,Angular / Vue / Web Components 同理)。CSF Next 目前处于实验阶段,原文片段在 tab 标题中以 🧪 标识,生产项目按官方成熟度提示决定是否采用。

两种方式背后的等价关系

从仓库文档对两种模式的描述可以看出,它们的语义是同一件事的两种语法外壳:CSF 3 用 loaders: [mswLoader()] 表达"项目级全局 Loader",CSF Next 用 addons: [addonMsw()] 表达"为项目注册一个 addon"——二者最终都会让 MSW 拦截能力覆盖所有 Story。迁移工具 npx msw-storybook-migrate 能帮你在这两种形态之间完成 v2→v3 的转换,避免手工改写出错。

初始化前的三步配套配置

preview 中的注册是 MSW addon 接入的"最后一步",在此之前需要完成三项前置配置(详见 mocking-network-requests.mdx 的 Set up the MSW addon 小节)。

1. 安装依赖

MSW addon 与 MSW 本体需要一并安装为开发依赖(msw-addon-install.md),按包管理器选择命令:

npm install msw msw-storybook-addon --save-dev
pnpm add msw msw-storybook-addon --save-dev
yarn add msw msw-storybook-addon --save-dev

2. 生成 Service Worker 文件

若项目尚未使用过 MSW,需要生成 MSW 所需的 Service Worker 文件(msw-generate-service-worker.md):

npx msw init ./public --save

Angular 项目通常需要调整目标目录(例如保存到 src),以确保产物被框架正确托管;--save 参数会把相关配置写入 package.json,避免提交后丢失。

3. 通过 staticDirs 托管生成文件

默认情况下生成的 Service Worker 位于项目的 /public,Storybook 需要通过 staticDirs 把它作为静态资源提供给浏览器加载(main-config-static-dirs.md)。在 .storybook/main.js(或 .storybook/main.ts)中配置:

export default {
  framework: '@storybook/your-framework', // 替换为实际框架包名
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  staticDirs: ['../public', '../static'],
};

若你的 MSW 文件输出到别的目录,这里需同步指向该目录;staticDirs 的更多取值规则参见 staticDirs 文档

初始化完成后如何编写 Mock Story

preview 初始化只负责"接通能力",具体每条 Story 要 Mock 哪些请求,则由 Story 级配置决定。完成上面的注册后,你可以在 YourPage.stories.ts 中通过 beforeEach 拿到注入的 msw 上下文对象,用 msw.use(...) 注册请求处理器。

以 REST 请求为例(msw-addon-configure-handlers-http.md),一个成功、一个失败的 Story 写法如下:

import { http, HttpResponse, delay } from 'msw';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { DocumentScreen } from './YourPage';

const meta = {
  component: DocumentScreen,
} satisfies Meta<typeof DocumentScreen>;

export default meta;
type Story = StoryObj<typeof meta>;

const TestData = {
  user: { userID: 1, name: 'Someone' },
  document: {
    id: 1,
    title: 'Something',
    brief: 'Lorem ipsum dolor sit amet.',
    status: 'approved',
  },
};

export const MockedSuccess: Story = {
  beforeEach({ msw }) {
    msw.use(
      http.get('https://your-restful-endpoint/', () => {
        return HttpResponse.json(TestData);
      }),
    );
  },
};

export const MockedError: Story = {
  beforeEach({ msw }) {
    msw.use(
      http.get('https://your-restful-endpoint', async () => {
        await delay(800);
        return new HttpResponse(null, { status: 403 });
      }),
    );
  },
};

这里 beforeEach 支持多层作用域——Story 级、组件(meta)级乃至项目级(preview.ts)均可定义,层级越高覆盖范围越广。除了 REST,msw.use(graphql.query('AllInfoQuery', ...)) 同样可用于拦截 GraphQL 查询,成功与失败分支分别返回 dataerrors(示例见 msw-addon-configure-handlers-graphql.md)。若你仍在使用 v2 的 msw 参数(非 beforeEach)定义 Handler,v3 下应改用上述新语法,或直接运行 npx msw-storybook-migrate 完成自动迁移。

小结

把 MSW addon 接入 Storybook 的关键动作集中在 .storybook/preview 一处:

  • CSF 3:从 msw-storybook-addon/csf3 导入 mswLoader,以项目级 loaders: [mswLoader()] 注册到默认导出的 Preview 配置;
  • CSF Next(实验性):从 msw-storybook-addon 导入 addonMsw,在框架包 definePreviewaddons 数组中注册 addonMsw()
  • 前置条件:安装 mswmsw-storybook-addon、用 msw init 生成 Service Worker、在 staticDirs 中托管产物;
  • 落地效果:注册后任意 Story 都能通过 beforeEach({ msw }) 精确 Mock REST / GraphQL 请求,实现组件在无真实后端下的隔离渲染与测试。

各代码变体与初始化注释原样保留于 msw-addon-initialize.md,完整实践教程见 mocking-network-requests.mdx,可直接对照查阅更多框架的完整示例。

登录后查看全文
热门项目推荐
相关项目推荐