ARTICLE DETAIL

资讯详情

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

Storybook 集成 TanStack React 框架:从 @storybook/react-vite 迁移到 @storybook/tanstack-react 的完整指南

Storybook 集成 TanStack React 框架:从 @storybook/react-vite 迁移到 @storybook/tanstack-react 的完整指南 Storybook 集成 TanStack React 框架从 storybook/react-vite 迁移到 storybook/tanstack-react 的完整指南【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook在 Storybook 官方框架体系中storybook/tanstack-react是专为基于 TanStack Router 与 TanStack Start 构建的 React Vite 应用设计的框架集成。本文以在.storybook/main.ts中将框架从storybook/react-vite切换为storybook/tanstack-react为核心操作系统讲解安装步骤、CSF 3 与 CSF Next 两种配置写法、自动化迁移工具以及迁移后如何利用路由感知渲染、自动 Mock 与 TanStack Query 集成能力帮助你在 Storybook 中无需启动完整应用即可独立开发、测试和文档化依赖路由与服务端函数Server Functions的组件。一、为什么需要 storybook/tanstack-reactstorybook/tanstack-react是 Storybook 针对 TanStack Router 和 TanStack Start 应用提供的框架集成它建立在storybook/react-vite参见 docs/get-started/frameworks/react-vite.mdx之上额外带来三层核心能力来源docs/get-started/frameworks/tanstack-react.mdx路由感知渲染自动用内存路由Memory-backed Router包裹每个 Story提供可用的 Router 上下文无需启动完整应用外壳自动 Router Mock将tanstack/react-router的导入重定向到 Storybook 兼容的 Mock 层useNavigate、useSearch、useParams等 Hook 在 Story 中照常可用导航行为可被观测TanStack Start Mock自动桩替换 Start 服务端与运行时入口点使依赖 Server Functions 的组件可以直接在 Storybook 中渲染。从仓库源码看该框架位于 code/frameworks/tanstack-react其 package.json 中明确声明依赖storybook/builder-vite、storybook/react、storybook/react-vite并在 peerDependencies 中要求tanstack/react-router、tanstack/react-start可选、tanstack/router-core、tanstack/start-client-core可选以及 React 与 Vite参见 package.json。二、环境要求与安装环境要求官方文档docs/get-started/frameworks/tanstack-react.mdx给出的最低要求为React≥ 18Vite≥ 7同时要求项目中已存在 TanStack Router 应用tanstack/react-router可用如果应用使用了 TanStack Start 的 API如 Server Functions还需要保留对应的 TanStack Start 包。从源码的 peerDependencies 看该框架实际声明的范围更宽Vite^5 || ^6 || ^7 || ^8React^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0但建议以官方文档的 React ≥ 18、Vite ≥ 7 作为稳妥基线。安装框架包在项目根目录执行安装来源docs/_snippets/tanstack-react-install.mdnpm install --save-dev storybook/tanstack-reactpnpm add --save-dev storybook/tanstack-reactyarn add --dev storybook/tanstack-react如果你是全新项目也可以直接使用 Storybook 的创建命令初始化见 docs/get-started/frameworks/tanstack-react.mdx 中的create命令然后从编写 Story、运行测试、编写文档开始。三、核心操作在 .storybook/main.ts 中切换框架将storybook/react-vite切换为storybook/tanstack-react的核心操作是修改.storybook/main.ts中的framework属性。根据你的配置写法有两种形式来源docs/_snippets/tanstack-react-add-framework.md。形式一CSF 3经典配置对象- import type { StorybookConfig } from storybook/react-vite; import type { StorybookConfig } from storybook/tanstack-react; const config: StorybookConfig { // ... - framework: storybook/react-vite, framework: storybook/tanstack-react, }; export default config;形式二CSF NextdefineMain 工厂函数- import { defineMain } from storybook/react-vite/node; import { defineMain } from storybook/tanstack-react/node; export default defineMain({ // ... - framework: storybook/react-vite, framework: storybook/tanstack-react, });注意两个细节类型导入同步切换CSF 3 场景下StorybookConfig类型要从storybook/tanstack-react导入CSF Next 场景下defineMain要从storybook/tanstack-react/node导入。这与框架包的导出结构一一对应——从 package.json 的exports字段可以看到包根路径导出主入口./node导出 Node 侧入口供 CSF 工厂使用此外还暴露了./preset、./preview、./react-router、./start、./start-storage-context等子路径。framework 属性可以携带 options当你的.storybook/main.ts需要向 Vite 构建器传参时可把framework写成对象形式来源docs/_snippets/tanstack-react-framework-options.mdimport type { StorybookConfig } from storybook/tanstack-react; const config: StorybookConfig { framework: { name: storybook/tanstack-react, options: { builder: { // Vite builder options }, }, }, }; export default config;import { defineMain } from storybook/tanstack-react/node; export default defineMain({ framework: { name: storybook/tanstack-react, options: { builder: { // Vite builder options }, }, }, });其中options.builder的类型为Recordstring, any用于配置框架底层的 Vite 构建器详见 docs/builders/vite.mdx 与 docs/api/main-config/main-config-framework.mdx。在源码层面corepreset 会把该builder选项透传给storybook/builder-vite见 src/preset.ts。四、同步更新 preview 与类型引用切换框架后.storybook/preview.*中的类型导入也需要同步更新保证类型检查一致来源docs/_snippets/tanstack-react-preview-migrate.md- import type { Preview } from storybook/react-vite; import type { Preview } from storybook/tanstack-react; const preview: Preview { //... }; export default preview;- import { definePreview } from storybook/react-vite; import { definePreview } from storybook/tanstack-react; export default definePreview({ //... });同时你的 Story 文件*.stories.*中Meta、StoryObj类型也应从storybook/tanstack-react导入以启用parameters.tanstack.router的类型安全下文会看到具体示例。五、自动化迁移npx storybook automigrate除了手工修改Storybook 还提供了自动化迁移工具npx storybook automigrate react-vite-to-tanstack-react根据官方文档docs/get-started/frameworks/tanstack-react.mdx该工具依次执行更新package.json将storybook/react-vite替换为storybook/tanstack-react更新.storybook/main.js|ts中的 framework 属性同时兼容普通配置与 CSF 工厂defineMain配置扫描并更新所有引用storybook/react-vite的 import 语句包括 CSF 工厂使用的storybook/react-vite/node覆盖 Story 文件与 Storybook 配置文件检测.storybook/preview.*、其余.storybook/目录及所有*.stories.*中的手动 TanStack Router 装饰器发现后会提供可复制的 AI 提示词引导 AI 助手删除已冗余的装饰器。迁移后的清理删除手动 Router 装饰器这一点需要特别强调storybook/tanstack-react已经自动为每个 Story 包裹 TanStack Router因此迁移后任何手写的RouterProvider/createRouter/createMemoryHistory/createRootRoute装饰器都应删除。需要指定路由的 Story请改用parameters.tanstack.router见下一节而不是手写装饰器。六、迁移后能做什么parameters.tanstack.router 全参数指南切换框架后你可以在 Story 的parameters.tanstack.router命名空间下声明路由行为。以下是该框架贡献的全部参数来源docs/get-started/frameworks/tanstack-react.mdx 的 API 章节。参数类型说明routeAnyRoute \| route options object直接传入一个 Route 实例或用一个包含path等选项的对象创建一个临时 Story 路由Storybook 会自动从路由中提取 React 组件pathstring设置 Story 路由的初始 URL 路径也支持携带#fragmentqueryRecordstring, unknown向初始 URL 追加搜索参数如?tabdetailspage2paramsResolveParamsPath将路由参数插值到当前路径当route是类型化文件路由时类型会被约束为该路由路径声明的参数名例如/$id对应{ id: string }routeOverridesPartialRecordstring, RouteOverrideOptions按路由 ID 覆盖路由选项作用于 Story 路由与根路由用__root__定位根路由可覆盖loader、beforeLoad、validateSearch、loaderDeps、contextcontextRecordstring, unknown \| (({ storyContext }) Recordstring, unknown)注入 Story 路由的路由上下文支持静态对象或接收 storyContext 的工厂函数工厂在路由初始加载之前、React 渲染之外运行因此其值可被loader与beforeLoad读取useRouterContext({ storyContext }) RouterContext在渲染期间以 React Hook 方式计算路由上下文适合读取只能从 React Provider 获取的值如useQueryClient()6.1 渲染一个 Route将 TanStack Route 对象通过parameters.tanstack.router.route提供给 Story来源docs/_snippets/tanstack-react-route-story.mdimport type { Meta, StoryObj } from storybook/tanstack-react; import { Route } from ./Page; const meta { parameters: { layout: fullscreen, tanstack: { router: { route: Route, // 在这里提供 Route // 其余属性均为类型安全 params: { id: 42 }, query: { tab: details }, }, }, }, } satisfies Metatypeof Route; export default meta; type Story StoryObjtypeof meta; export const Default: Story {}; export const WithCustomLoader: Story { parameters: { tanstack: { router: { route: Route, // 在这里提供 Route params: { id: 42 }, routeOverrides: { /items/$id: { loader: async () ({ item: { id: 42, name: Loaded inside Storybook }, }), }, }, }, }, }, };6.2 处理动态参数如 /$idparams对象会被插值进 URLrouteOverrides则让你在不改动原始路由对象的前提下桩替换 loader来源docs/_snippets/tanstack-react-dynamic-params.mdimport type { Meta } from storybook/tanstack-react; import { Route } from ./$id; const meta { parameters: { tanstack: { router: { route: Route, params: { id: 42 }, routeOverrides: { /showcase/$id: { loader: () ({ item: mockItem }), }, }, }, }, }, } satisfies Metatypeof Route; export default meta;6.3 渲染嵌套路由当route是连接到应用路由树route tree的文件路由时Storybook 会自动向上回溯到根并复制整条路由树父级布局路由如_authenticated认证外壳也会随之渲染。你也可以直接传入routeTree.gen.ts导出的routeTree来源docs/_snippets/tanstack-react-route-tree-story.mdimport type { Meta, StoryObj } from storybook/tanstack-react; // 路由文件是应用路由树的一部分 import { Route } from ./routes/_authenticated/settings/profile; const meta { parameters: { tanstack: { router: { // Storybook 向上回溯到根并复制整条路由树 // 因此父级布局如认证外壳也会渲染 route: Route, path: /settings/profile, // 桩替换父级路由的守卫让 Story 可以独立渲染 routeOverrides: { /_authenticated: { beforeLoad: () {} }, }, }, }, }, } satisfies Metatypeof Route; export default meta; type Story StoryObjtypeof meta; export const Default: Story {};6.4 对普通组件使用路由参数如果 Story 渲染的是普通 React 组件而非路由对象仍可通过parameters.tanstack.router提供路由上下文——组件可以正常读取useRouterState、useSearch、useParams、useLoaderData等 Hook来源docs/_snippets/tanstack-react-plain-component-story.mdimport type { Meta, StoryObj } from storybook/tanstack-react; import { Page } from ./Page; const meta { component: Page, } satisfies Metatypeof Page; export default meta; type Story StoryObjtypeof meta; export const Default: Story { parameters: { tanstack: { router: { route: { path: /demo/form/address, }, query: { view: list }, }, }, }, };6.5 定义搜索参数与 URL 片段hash用query定义搜索参数如?tabdetailspage2用path定义 URL 片段如#section-name来源docs/_snippets/tanstack-react-query-and-path.mdexport const WithHash: Story { parameters: { tanstack: { // 为路由提供 URL 片段hash router: { path: /#section-name }, }, }, }; export const WithSearch: Story { parameters: { tanstack: { // 为路由提供查询字符串 router: { query: { tab: details, page: 2 } }, }, }, };6.6 按 Story 覆盖路由选项当路由的loader或beforeLoad会调用真实 API 时可以在不修改原始路由对象的前提下通过routeOverrides按 Story 覆盖。每个 key 是一个路由 ID值可覆盖loader、beforeLoad、validateSearch、loaderDeps、context用__root__定位根路由来源docs/_snippets/tanstack-react-route-tree-overrides.mdconst meta { title: Users/UserCard, parameters: { tanstack: { router: { route: Route, params: { userId: 42 }, // 覆盖路由的 loader让 Story 不调用真实 API routeOverrides: { /users/$userId: { loader: async () ({ user: { id: 42, name: Ada Lovelace } }), }, }, }, }, }, } satisfies Metatypeof Route;七、底层原理自动 Mock 是如何实现的从源码看该框架的自动 Mock 能力由 src/preset.ts 中的viteFinal统一装配。它在storybook/react-vite的 Vite 配置基础上追加了三个自定义插件moduleInterceptionPluginsrc/plugins/module-interception.ts在模块解析阶段把tanstack/react-router及其子路径导入重定向到storybook/tanstack-react/react-routerMock 模块同时拦截tanstack/react-start、tanstack/react-start/server、tanstack/react-start-server、tanstack/start-server-core等 Start 服务端模块以及virtual:cloudflare、server-entry、worker-entry等虚拟模块防止它们进入浏览器serverCodeEliminationPluginsrc/plugins/server-code-elimination.ts做服务端代码消除serverOnlyStubPluginsrc/plugins/server-only-stub.ts桩替换服务端专用模块。此外preset 还通过previewAnnotations注入了storybook/tanstack-react/previewsrc/preview.tsx并通过optimizeViteDeps预优化了tanstack/react-router的依赖链。对外框架包暴露了./react-router与./start两个子路径见 package.json 与 src/export-mocks分别提供 TanStack Router 与 TanStack Start 兼容的 Mock 实现包括 Mock 化的createServerFn()。测试中如需断言导航 Spy可显式从这些模块导入。八、在 Story 中 Mock Server Functions如果组件导入了 TanStack Start 的 Server FunctionStorybook 会把createServerFn().handler(...)的结果变成 Mock 函数你可以用标准 Mock API 按 Story 覆盖从而无需改动应用代码即可展示加载、成功、失败等状态来源docs/_snippets/tanstack-react-mock-server-fn-stories.mdimport type { Meta, StoryObj } from storybook/tanstack-react; import { expect, mocked } from storybook/test; import { updateProfile } from ../lib/updateProfile; import { ProfileForm } from ./ProfileForm; const meta { component: ProfileForm, } satisfies Metatypeof ProfileForm; export default meta; type Story StoryObjtypeof meta; export const Success: Story { beforeEach: async () { mocked(updateProfile).mockResolvedValue({ ok: true, name: Ada Lovelace }); }, play: async ({ canvas, userEvent }) { await userEvent.type(canvas.getByLabelText(Name), Ada Lovelace); await userEvent.click(canvas.getByRole(button, { name: Save profile })); await expect(updateProfile).toHaveBeenCalled(); }, }; export const Failure: Story { beforeEach: async () { mocked(updateProfile).mockRejectedValue(new Error(Could not save profile)); }, };九、处理 Server-only 依赖三层策略TanStack Start 应用常在路由文件的模块作用域内导入服务端专用包如数据库客户端、认证库这些包在浏览器中会崩溃。该框架分三层处理第一层框架级 Mock自动。preset 已拦截tanstack/react-start、tanstack/react-start/server、tanstack/start-storage-context等模块并把createServerFn()处理器替换为 Mock 函数无需你做任何事。第二层应用级服务端模块。当路由导入了应用自己的服务端代码如~/db/client、~/auth/index.server时需要用__mocks__文件阻止真实模块及其 Node.js 依赖加载进浏览器。第 1 步在.storybook/preview.ts注册 Mock来源docs/_snippets/tanstack-react-mock-module-preview.mdimport { sb } from storybook/test; // 阻止 postgres仅限 Node加载进浏览器 sb.mock(import(../src/db/client.ts)); export default {};第 2 步在真实模块旁创建src/db/__mocks__/client.ts只使用import type避免引入任何服务端包。这里要解释清楚为什么用__mocks__文件而不是自动 MockStorybook 的自动 Mock 只替换函数但仍会求值原始模块及其导入。对于导入了postgres、pg等 Node.js 专属包的模块原始模块绝不能被求值否则浏览器会崩溃__mocks__文件是唯一能完全阻止原始模块及其依赖链求值的方案详见 docs/writing-stories/mocking-data-and-modules/mocking-modules.mdx。第三层识别需要 Mock 的模块。报错如does not provide an export named default或AsyncLocalStorage is not defined说明服务端专用模块进入了浏览器。修复方法是 Mock服务端模块本身而不是使用它的组件或路由。例如Dashboard.tsx导入~/auth/session~/auth/session导入~/db/client~/db/client导入postgres——就 Mock~/db/client。Node.js 依赖postgres是冒烟枪Mock 离它最近、且由你掌控的模块即可。两种不需要 Mock 的情况模块来自tanstack/*——框架 preset 已处理请确保storybook/tanstack-react是最新版本模块只导入了createServerFn——已被 Mock报错来自同一文件中的其他导入。十、与 TanStack Query 协同使用该框架可与 TanStack Query 配合在 Storybook 中提供可用的 QueryClient 并按 Story 预置查询数据。TanStack Query 不会被自动配置官方推荐方案是在 preview 文件中创建单一QueryClient通过beforeEach在 Story 之间清空缓存并让同一个实例同时进入parameters.tanstack.router.context和QueryClientProvider装饰器来源docs/_snippets/tanstack-react-query-setup.mdimport { type QueryClient, QueryClientProvider } from tanstack/react-query; import type { Preview } from storybook/tanstack-react; // 创建新的 QueryClient const queryClient new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity, }, }, }); const preview: Preview { beforeEach: () { // 在 Story 之间清空缓存让每个 Story 从全新状态开始 queryClient.clear(); }, parameters: { tanstack: { router: { // 让 queryClient 通过 ctx.context.queryClient 在 Story 的 beforeEach 中可用 context: { queryClient }, }, }, }, decorators: [ (Story) ( // 向所有 Story 提供 QueryClient QueryClientProvider client{queryClient} Story / /QueryClientProvider ), ], }; export default preview;这样无论 Storybook 以何种方式渲染 Story侧边栏、Docs 页、portable stories 或测试运行路由上下文与 React Provider 都指向同一个 QueryClient每个 Story 在渲染前清空缓存获得全新的查询状态。在单个 Story 中用beforeEach对共享 QueryClient 调用setQueryData预置数据通过parameters.tanstack.router.context取出实例来源docs/_snippets/tanstack-react-query-in-story.mdexport const LoggedIn: Story { beforeEach: async ({ parameters }) { const qc: QueryClient parameters.tanstack?.router?.context?.queryClient; qc?.setQueryData([currentUser], { id: user-1, name: Ada Lovelace, }); }, };如果需要更强的隔离例如在同一个 Docs 页面上渲染多个使用相同 query key 的 Story 并保持各自缓存独立也可以为每个 Story 创建独立的 QueryClient但必须让路由上下文与QueryClientProvider指向同一个实例并显式清理每个客户端持有的定时器、订阅与缓存数据。十一、FAQ 与常见问题何时用storybook/tanstack-react而非storybook/react-vite当组件依赖 TanStack Router 或 TanStack Start 的 API且需要 Storybook 提供路由上下文、类型化路由参数、自动 Router Mock 与 Mock 化的 Start Server Function 行为时使用前者参见 docs/get-started/frameworks/react-vite.mdx标准 React Vite 且不使用 TanStack Router 的项目继续使用storybook/react-vite。样式在 Storybook 中丢失怎么办在.storybook/preview.*中导入应用 CSS使其随 preview 一起打包import ../src/styles/app.css;详见 docs/configure/styling-and-css.mdx。如何为所有 Story 提供 React Context Provider主题、toast、认证等使用项目级装饰器docs/writing-stories/decorators.mdx 中的全局装饰器为所有 Story 提供 Provider也可以在组件级或 Story 级装饰器中按需提供。支持 React Server Components 吗不支持。该框架使用内存路由在浏览器中运行 Story而 React Server Components 需要服务端运行时。如果组件是 Server Component请将客户端部分提取为 Client Component 再编写 Story。报错模块未提供 default 导出通常是服务端专用模块被导入进了浏览器。按第九节的流程沿错误堆栈定位模块并添加 Storybook Mock。十二、小结从storybook/react-vite切换到storybook/tanstack-react本质上是三步安装框架包、修改.storybook/main.ts的framework属性CSF 3 与 CSF Next 两种写法、同步preview.*与 Story 文件的类型导入。之后你便获得了内存路由包裹、路由感知渲染、parameters.tanstack.router全参数控制、TanStack Router/Start 自动 Mock、Server-only 依赖三层治理以及与 TanStack Query 的无缝协同——这些能力在源码层由 preset.ts 中的 Vite 插件链模块拦截、服务端代码消除、服务端专用模块桩替换支撑实现。仓库还提供了可直接参考的框架内置示例 Story见 template/stories包含Outlet、PathlessLayout、RouterContextInjection、LoaderContextInjection等场景可以作为上手与对照的最佳实践模板。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表