Storybook Next.js 框架:用 `nextjs.navigation.segments` 为 App Router 导航 Hook 配置路由分段
Storybook Next.js 框架用nextjs.navigation.segments为 App Router 导航 Hook 配置路由分段【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook本篇技术指南聚焦 Storybook 官方 Next.js 框架预设storybook/nextjs/storybook/nextjs-vite中nextjs.navigation.segments参数的用法如何为依赖useSelectedLayoutSegment、useSelectedLayoutSegments与useParams等next/navigationHook 的组件在 Story 的 meta 参数中声明路由分段segments使组件在 Story 中拿到确定性的路由数据。读完本文你将掌握该参数的完整配置写法含 CSF 3 与 CSF Next 两种语法、参数类型与默认值、useParams的[key, value]分段格式并能从源码层面理解 Storybook 是如何把这些参数转成 Next.js App Router 的 React Context 的。背景为什么 App Router 组件需要在 Story 中配置导航参数next/navigation只能在app目录下的组件/页面中使用Pages Router 对应的next/router只能用于pages目录。而 Storybook 渲染 Story 时并没有真正的 Next.js 路由系统框架预设会把这些 API 自动 stub 成 mock 实现交互事件如push()会记录到 Actions 面板。为了让useParams、useSelectedLayoutSegment这类从路由上下文取值的 Hook 返回有意义的值官方文档 Next.js Navigation 章节 要求只要 Story 中导入的组件使用了next/navigation就需要通过nextjs命名空间的参数提供路由信息其中segments正是控制布局分段返回值的关键项。本文对应的官方文档片段为 nextjs-navigation-segments-override-in-meta.md它被嵌入到 nextjs.mdx 与 nextjs-vite.mdx 两篇框架文档的 “useSelectedLayoutSegment、useSelectedLayoutSegmentsanduseParamshooks” 小节中。前置条件开启nextjs.appDirectorysegments生效的前提是框架预设进入 App Router 分支。需要在 Story 的参数中设置nextjs.appDirectory: true默认为false。它同样遵循参数继承规则可以写在单个 Story、组件的 meta 或项目的.storybook/preview.tsx中import NavigationBasedComponent from ./NavigationBasedComponent; const meta { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, // 开启 App Router 导航上下文 }, }, }; export default meta;如果整个 Next.js 项目的所有页面都在app目录没有pages目录可以直接把nextjs.appDirectory设为true写入 preview 文件让全部 Story 生效。核心配置在 meta 中声明nextjs.navigation.segments片段文档给出的完整配置如下CSF 3 TypeScript 写法将your-framework替换为nextjs或nextjs-vite// Replace your-framework with nextjs or nextjs-vite import type { Meta, StoryObj } from storybook/your-framework; import NavigationBasedComponent from ./NavigationBasedComponent; const meta { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [dashboard, analytics], }, }, }, } satisfies Metatypeof NavigationBasedComponent; export default meta;CSF 3 JavaScript 写法import NavigationBasedComponent from ./NavigationBasedComponent; export default { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [dashboard, analytics], }, }, }, };仓库片段中同时提供了实验性的 CSF Nextpreview.meta()写法便于使用新版工厂语法的用户对齐import preview from ../.storybook/preview; import NavigationBasedComponent from ./NavigationBasedComponent; const meta preview.meta({ component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [dashboard, analytics], }, }, }, });配置后 Hook 的返回值按官方文档 nextjs.mdx 的说明使用segments: [dashboard, analytics]后Story 中渲染的组件从三个 Hook 拿到的值如下import { useSelectedLayoutSegment, useSelectedLayoutSegments, useParams } from next/navigation; export default function NavigationBasedComponent() { const segment useSelectedLayoutSegment(); // dashboard const segments useSelectedLayoutSegments(); // [dashboard, analytics] const params useParams(); // {} // ... }即纯字符串分段只填充布局分段segment/segmentsuseParams()返回空对象。nextjs.navigation.segments未设置时的默认值是[]。想让useParams有值使用[key, value]分段要模拟动态路由参数segments数组中的元素需要改为“包含两个字符串的数组”——第一个是参数键第二个是参数值// Replace your-framework with nextjs or nextjs-vite import type { Meta, StoryObj } from storybook/your-framework; import NavigationBasedComponent from ./NavigationBasedComponent; const meta { component: NavigationBasedComponent, parameters: { nextjs: { appDirectory: true, navigation: { segments: [ [slug, hello], [framework, nextjs], ], }, }, }, } satisfies Metatypeof NavigationBasedComponent; export default meta;此时组件中import { useSelectedLayoutSegment, useSelectedLayoutSegments, useParams } from next/navigation; export default function ParamsBasedComponent() { const segment useSelectedLayoutSegment(); // hello const segments useSelectedLayoutSegments(); // [hello, nextjs] const params useParams(); // { slug: hello, framework: nextjs } // ... }可以看出[key, value]配对会以键名进入useParams()的返回值同时其值hello、nextjs也会被作为分段值反映在useSelectedLayoutSegment(s)中。nextjs.navigation参数类型与默认值框架文档在参数参考部分见 nextjs.mdx 的 Parameters 章节给出的navigation类型为{ asPath?: string; pathname?: string; query?: Recordstring, string; segments?: (string | [string, string])[]; }默认值为{ segments: []; }也就是说segments支持“纯字符串分段”与[key, value]参数分段的混合数组pathname、query、asPath则用于覆盖导航上下文的其余字段默认导航上下文为{ pathname: /, query: {} }。参数继承同一配置可放三个层级文档明确说明这类覆盖可以应用到单个 Story、单个组件的全部 Storymeta或整个项目project parameters遵循标准的参数继承规则。因此实践中常见两种组织方式组件级在 meta 中固定appDirectory: true与该组件的默认segments保证每个 Story 都可渲染依赖路由的组件Story 级对个别 Story 再叠加navigation覆盖框架会把 Story 参数浅合并进导航上下文。例如官方片段 nextjs-navigation-override-in-story.md 展示了对单个 Story 覆盖pathname: /profile与query: { user: 1 }的写法项目级在 preview 中统一提供nextjs.appDirectory与默认navigation。源码剖析segments 如何变成next/navigation上下文从源码结构看上述配置的消费链路集中在storybook/nextjs的 routing 模块。1. 装饰器按appDirectory分流RouterDecorator 读取parameters.nextjs?.appDirectory缺省为false为真时用AppRouterProvider包裹 Story并把导航参数浅合并进默认路由参数后传入否则回落到 Pages Router 的PageRouterProviderconst defaultRouterParams: RouteParams { pathname: /, query: {}, }; // ... AppRouterProvider routeParams{{ ...defaultRouterParams, ...parameters.nextjs?.navigation, }} 这解释了文档中“框架会把你写在nextjs.navigation里的内容浅合并进 router”的说法segments、pathname、query等字段直接成为routeParams的组成部分。2.AppRouterProvider把 segments 构造成路由树app-router-provider.tsx 中的处理分两条线布局分段getParallelRoutes将segments字符串列表逐个shift并嵌套构造成 Next.js 的FlightRouterState树[pathname, { children: [segment, { children: [...] }] }]挂到GlobalLayoutRouterContext/LayoutRouterContext上useSelectedLayoutSegment(s)就是基于这棵树取值的——所以[dashboard, analytics]会让useSelectedLayoutSegment()得到dashboard、useSelectedLayoutSegments()得到完整列表动态参数组件用useMemo遍历routeParams.segments凡形如长度为 2、首元素为字符串的[key, value]配对都会写入params[key] value最终通过PathParamsContext.Provider提供给useParams()。这正是“[key, value]分段让useParams返回{ slug, framework }”的底层依据。源码中还兼容了对象形式的segments键值对直接映射为参数从源码结构看这类非数组形态在文档中未作为推荐用法但实现上是支持的。此外该 Provider 同时注入了PathnameContext、SearchParamsContext由new URLSearchParams(query)构造以及AppRouterContext值为getRouter()返回的 mock router使得usePathname()、useSearchParams()、useRouter()等其余next/navigation导出都能工作。3. 预览端 loader 负责创建 mockpreview.tsx 的 loader 同样以appDirectory作为分支条件为真时调用createNavigation(router)否则调用createRouter(...)。createNavigation定义在 export-mocks/navigation/index.ts它把push、replace等 router 方法生成为带动作名的 mock 函数如next/navigation::useRouter().push因此路由交互会自动出现在 Actions 面板也可以用常规 mock API 在 play 函数中做断言。小结与实用建议依赖next/navigationHook 的组件先确保nextjs.appDirectory: true生效否则框架根本不会挂载 App Router 上下文只需要布局分段时用纯字符串segments: [a, b]需要useParams取值时改写为[[key, value], ...]配对segments默认为[]useParams()默认为{}写 Story 断言前先确认这两点参数支持 Story / meta / preview 三个层级的继承与覆盖组件级默认 Story 级差异覆盖是较常用的组合方式想验证配置是否生效除直接读取 Hook 返回值外还可在 Actions 面板观察路由方法被 mock 记录的情况或结合框架导出的navigation.mockgetRouter()在 play 函数中断言。以上写法均与当前仓库文档及storybook/nextjs源码实现一致若同时使用nextjs-vite框架参数与行为相同仅 Story 文件的类型导入包名不同。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →