ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Storybook Next.js 框架指南:在 preview 中覆盖默认 Router 并接管路由 Mock 行为

Storybook Next.js 框架指南:在 preview 中覆盖默认 Router 并接管路由 Mock 行为 Storybook Next.js 框架指南在 preview 中覆盖默认 Router 并接管路由 Mock 行为【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook本文讲解 Storybook 的storybook/nextjs/storybook/nextjs-vite框架如何为next/router提供自动 Stub以及如何通过.storybook/preview.tsx中的parameters.nextjs.router覆盖默认路由状态如basePath、在beforeEach中用getRouter()接管push、replace等路由方法的 Mock 实现。读完本文你能掌握从“全局参数覆盖路由默认值”到“逐故事细粒度覆盖”的完整配置路径并能结合仓库源码理解覆盖值是如何被合并进 Mock 单例路由的。一、背景Storybook 如何 Stub Next.js 的 Router在 Storybook 中渲染依赖路由的 Next.js 组件例如通过useRouter()读取pathname、query或点击链接触发push时真实的路由环境并不存在。Storybook 的 Next.js 框架 preset 会自动为next/router创建一个 Stub路由的状态字段pathname、asPath、query等使用一套确定的默认值路由的方法push、replace、reload、back、forward、prefetch、beforePopState以及events.on/off/emit全部是 Mock 函数组件与路由的每次交互都会自动记录到 Actions 面板可以直接观察方法 Mock 基于 Storybook 的storybook/test提供的fn构造因此支持所有标准 Mock APImockImplementation、mock.calls、expect(...).toHaveBeenCalled等。源码层面的证据在 router Mock 工厂defaultRouterState定义了默认路由状态createRouter为每个动作方法生成带mockName的 Mock并把结果写回next/dist/client/router.js的单例路由见 singletonRouter 覆写逻辑。需要区分两种路由体系详见 Next.js 框架文档的 routing 章节场景使用模块覆盖入口pages目录组件next/routerparameters.nextjs.router本文主题app目录组件next/navigation需先设置nextjs.appDirectory: true再通过parameters.nextjs.navigation覆盖二、默认 Router 的初始状态Stubbed router 的默认值如下locale特别说明见下// Default router const defaultRouter { // The locale should be configured globally: https://storybook.js.org/docs/essentials/toolbars-and-globals#globals locale: globals?.locale, asPath: /, basePath: /, isFallback: false, isLocaleDomain: false, isReady: true, isPreview: false, route: /, pathname: /, query: {}, };这与源码中的 defaultRouterState 一一对应唯一的差异点是locale它不写在默认对象里而是由框架在构造路由时从Storybook 的 globals注入。也就是说若你在 Storybook 的 globals 中配置了locale多语言项目的router.locale会自动跟随无需每个故事单独设置。另外router对象包含push()、replace()等全部原始方法且它们都是可用常规 Mock API 操作和断言的 Mock 函数。三、在 preview 中覆盖默认 Router本文核心配置有两类覆盖需求覆盖路由属性状态值例如把basePath改成/app/接管路由方法的行为例如改写push的执行逻辑。前者用parameters后者用beforeEach。3.1 TypeScript 写法preview.tsx在.storybook/preview.tsx中同时完成两类覆盖// Replace your-framework with nextjs or nextjs-vite import { getRouter } from storybook/nextjs/router.mock; export default { parameters: { nextjs: { // Override the default router properties router: { basePath: /app/, }, }, }, async beforeEach() { // Manipulate the default router method mocks getRouter().push.mockImplementation(() { /* ... */ }); }, };注意源码中loaders的取值链路见 preview loaders 实现框架会读取parameters.nextjs.router并以createRouter({ locale: globals.locale, ...router })的方式构造路由——框架把你写在参数里的内容浅合并shallow merge进默认路由。因此参数里只需写想覆盖的字段如basePath其余字段asPath、isReady等仍沿用默认值。getRouter()是 Mock 模块导出的访问器见 getRouter 实现它返回当前已创建的路由 Mock 单例若路由尚未创建比如 app 目录模式下未调用createNavigation前误用会抛出NextjsRouterMocksNotAvailable错误提示你导入了不适用的 Mock。3.2 JavaScript 写法preview.jsx// Replace your-framework with nextjs or nextjs-vite import type { Preview } from storybook/nextjs; // Must include the .mock portion of filename to have mocks typed correctly import { getRouter } from storybook/nextjs/router.mock; const preview: Preview { parameters: { nextjs: { // Override the default router properties router: { basePath: /app/, }, }, }, async beforeEach() { // Manipulate the default router method mocks getRouter().push.mockImplementation(() { /* ... */ }); }, }; export default preview;注释强调了一点关键实践导入路径必须包含.mock后缀storybook/nextjs/router.mock这样 Mock 才能拿到正确的类型定义。3.3 CSF Next 实验语法definePreview如果使用 CSF Next 实验特性同一套覆盖逻辑可以改用definePreview书写import { definePreview } from storybook/nextjs; // Must include the .mock portion of filename to have mocks typed correctly import { getRouter } from storybook/nextjs/router.mock; const preview definePreview({ parameters: { nextjs: { router: { basePath: /app/, }, }, }, async beforeEach() { getRouter().push.mockImplementation(() { /* ... */ }); }, }); export default preview;对应的definePreview.jsx变体写法与之相同仅将import type改为运行时导入其余结构不变。适用前提以上示例基于pages目录 next/router场景storybook/nextjs使用 webpack 构建storybook/nextjs-vite使用 Vite 构建只需替换包名。若你的组件位于app目录、使用next/navigation则应改用parameters.nextjs.navigation覆盖并先设置nextjs.appDirectory: true见 Next.js navigation 章节。四、覆盖的作用域与参数继承preview 级的覆盖只是作用域之一。nextjs.router遵循 Storybook 的标准参数继承规则见 Parameters 文档作用域配置位置适用场景项目级.storybook/preview.tsx的parameters.nextjs.router全站统一的路由环境如固定的basePath组件级故事文件默认导出的parameters.nextjs.router某组件的所有故事共享同一套路由故事级单个故事的parameters.nextjs.router仅某个故事需要特殊路由如/profile/[id]query: { id: 1 }故事级覆盖的示例完整片段见 nextjs-router-override-in-story.mdimport type { Meta, StoryObj } from storybook/nextjs; import RouterBasedComponent from ./RouterBasedComponent; const meta { component: RouterBasedComponent, } satisfies Metatypeof RouterBasedComponent; export default meta; type Story StoryObjtypeof meta; // Interact with the links to see the route change events in the Actions panel. export const Example: Story { parameters: { nextjs: { router: { pathname: /profile/[id], asPath: /profile/1, query: { id: 1, }, }, }, }, };从源码结构看createRouter的合并顺序是defaultRouterState → overrides → routerActions见 合并逻辑即默认状态打底参数覆盖项在上层展开方法 Mock 始终保留。由于是浅合并query这类对象字段整体替换而非深度合并——故事级query: { id: 1 }会完全覆盖默认的空query这正符合“按故事精确控制路由”的预期。此外createRouter还支持传入函数形式的动作覆盖如用overrides.push替换默认push实现见 overrides 处理而在 preview 中通过beforeEachgetRouter().push.mockImplementation(...)是更常见的运行时接管方式因为它在每个故事执行前生效便于按故事动态调整。五、验证与排查要点观察路由交互在故事页点击触发push/replace的链接Actions 面板会显示形如next/router::useRouter().push的调用事件——这正是各 Mock 的mockName见 mockName 列表可用于在 Actions 中过滤。断言路由行为由于方法是标准 Mock可以在play函数或组件测试中写expect(getRouter().push).toHaveBeenCalledWith(/app/login)。getRouter()抛错若在appDirectory: true的故事中调用getRouter()会因 page router Mock 未创建而抛出NextjsRouterMocksNotAvailable提示你使用的是next/navigation体系应改用对应的 navigation Mock。basePath覆盖的影响框架在 装饰器 中根据parameters.nextjs?.appDirectory决定注入PageRouterProvider还是AppRouterProviderpage 路由分支下parameters.nextjs.router的每个字段都会进入 Mock 路由直接影响useRouter()返回值。六、小结parameters.nextjs.router在 preview / meta / 故事三级作用域上浅合并覆盖默认路由状态locale自动来自 globalsgetRouter()从storybook/nextjs/router.mock导入务必带.mock后缀在beforeEach中接管push等方法 Mock 的行为覆盖仅作用于pages目录的next/router场景app目录请使用nextjs.appDirectorynextjs.navigation组合。相关延伸阅读Next.js 框架总览、Next.js (Vite) 框架总览、router Mock 源码、框架 preview 入口。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表