在 Storybook 中以 Provider 形式注入真实容器组件:Container/Context 分离模式的页面层 Mock 实战

发布时间:2026/10/30 1:45:33

在 Storybook 中以 Provider 形式注入真实容器组件:Container/Context 分离模式的页面层 Mock 实战 在 Storybook 中以 Provider 形式注入真实容器组件Container/Context 分离模式的页面层 Mock 实战【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook导读当组件从原子组件上升到页面Page层级时往往伴随着数据请求、浏览器 API、全局状态等已连接connected依赖直接放进 Storybook 渲染就必须挨个 Mock成本极高。Storybook 官方文档给出的一种解法是用 React/Solid 的 Context 作为容器组件的中转站——Storybook 里用故事的 Mock 版本替换容器应用里则通过ProfilePageContext.Provider注入真实实现。本文以docs/writing-stories/build-pages-with-storybook.mdx第 9 节为主线完整演示创建 Context → 展示组件消费容器 → Storybook 中 Mock → 应用侧 Provider 注入真实容器的端到端流程并补充对应的仓库代码片段作为可运行参考。为什么要在页面层避开直接 Mock 容器组件Storybook 支持构建从原子组件到组合页面的任何组件见 docs/writing-stories/build-pages-with-storybook.mdx。页面通常不会单独请求数据而是由内部各区块container完成。要渲染一个页面级 Story常见做法是对其依赖逐层 MockMocking modulesMock 组件文件中import进来的模块Mocking API services拦截 REST/GraphQL 网络请求Mocking providers用 Decorator 包裹并伪造 Context Provider 提供的值如主题、Redux 数据。这些方案各自有效但存在两个现实痛点繁琐容器组件可能内嵌在页面组件树的任意深度逐层传递数据、逐层 Mock import 会让工作量快速膨胀困难带有本地状态的容器组件本身很难被模拟Mock 往往无从下手。因此官方文档推荐一种结构性解法该段落在docs/writing-stories/build-pages-with-storybook.mdx#L62-L70先把页面做容器/展示的严格拆分再通过 Context 把需要使用哪些容器这件事从展示层解耦出来——页面组件以 Props/Context 拿到容器而非直接 import 它们。这样无论页面嵌套多深都可以在渲染前整体替换容器无需关心容器内部的数据请求与状态逻辑。模式总览应用侧一份真实、Storybook 侧一份 Mock整个模式由四个文件组成官方建议为每个页面或视图单独划分目录结构ProfilePage.js # 展示组件只负责布局通过 useContext 拿容器 ProfilePage.stories.js # 页面级 Story ProfilePageContainer.js # 容器组件真实数据逻辑网络请求等 ProfilePageContext.js # React/Solid Context 定义关键思想同一份ProfilePageContext在两侧提供不同实现。应用侧ProfilePageContext.Provider提供真实的UserPostsContainer、UserFriendsContainer见本主题关联片段 mock-context-container-provider.md在应用入口如pages/profile.js中使用Storybook 侧Provider 提供来自各子组件 Story 的Mock 版本组件往往本身就是理想的可替换实现见 mock-context-container.md若某容器几乎出现在每个页面可上升为全局容器 ContextGlobalContainerContext在.storybook/preview.js的全局 Decorator 中注入见 mock-context-container-global.md。第一步定义 Contextmock-context-create.mdContext 文件本身极其简单——它只导出一个空的 Context不包含任何业务逻辑。React 版本// ProfilePageContext.js import { createContext } from react; const ProfilePageContext createContext(); export default ProfilePageContext;Solid 版本仅换用solid-js的createContext// ProfilePageContext.js import { createContext } from solid-js; const ProfilePageContext createContext(); export default ProfilePageContext;该文件对应片段 mock-context-create.md。第二步展示组件通过 useContext 消费容器mock-context-in-use.mdProfilePage是纯展示组件它不做任何数据请求而是通过useContext从ProfilePageContext中取出两个容器组件再把来自上层 Props 的userId传交给它们// ProfilePage.jsReact import { useContext } from react; import ProfilePageContext from ./ProfilePageContext; export const ProfilePage ({ name, userId }) { const { UserPostsContainer, UserFriendsContainer } useContext(ProfilePageContext); return ( div h1{name}/h1 UserPostsContainer userId{userId} / UserFriendsContainer userId{userId} / /div ); };Solid 版本几乎一致仅 Props 读取方式遵循 Solid 的props风格// ProfilePage.jsSolid import { useContext } from solid-js; import ProfilePageContext from ./ProfilePageContext; export const ProfilePage (props) { const { UserPostsContainer, UserFriendsContainer } useContext(ProfilePageContext); return ( div h1{props.name}/h1 UserPostsContainer userId{props.userId} / UserFriendsContainer userId{props.userId} / /div ); };可以看到只要 Provider 提供了容器页面组件就能像普通组件一样自由渲染它们。展示层完全不感知容器的真实实现是网络请求、Redux 连接还是本地状态这正是后续可以整容器替换的前提。对应片段见 mock-context-in-use.md。第三步在 Storybook 中提供 Mock 容器mock-context-container.md在 Story 文件中我们不再构造真实容器而是为ProfilePageContext.Provider传入Mock 实现。其中最常见的技巧Mock 版本直接复用子组件自身 Stories 里的导出。既然 Story 本身就是给定 Props 渲染该组件的场景它天然就是一个可复用的 React 元素工厂多数情况下足以充当容器的替身。// ProfilePage.stories.js import React from react; import { ProfilePage } from ./ProfilePage; import { UserPosts } from ./UserPosts; // 从故事文件中引入某个具体 story import { Normal as UserFriendsNormal } from ./UserFriends.stories; export default { component: ProfilePage, }; const ProfilePageProps { name: Jimi Hendrix, userId: 1, }; const context { // 如果需要在这里可以访问到 userId prop UserPostsContainer({ userId }) { return UserPosts {...ProfilePageProps} /; }, // 大多数情况下直接把 story 传进来即可。 // 这里传入的是 UserFriends 组件故事中的 normal story 导出。 UserFriendsContainer: UserFriendsNormal, }; export const Normal { render: () ( ProfilePageContext.Provider value{context} ProfilePage {...ProfilePageProps} / /ProfilePageContext.Provider ), };两种 Mock 形态各有适用场景Mock 方式写法适用情况直接传入 story 导出UserFriendsContainer: UserFriendsNormal容器 Mock 不需要感知页面级 Props最简单、最常用函数形式包裹UserPostsContainer({ userId }) { return UserPosts .../ }需要根据页面 Props如userId动态拼接数据或注入其他 Props 时完整片段见 mock-context-container.md。如果同一个 Provider 值适用于ProfilePage的所有 Story则不必在每个 Story 里重复包裹 Provider官方建议改用Decorator收敛逻辑参见 docs/writing-stories/decorators.mdx。第四步在应用中通过 Provider 注入真实容器本主题核心片段页面在 Storybook 中可渲染之后还需要保证应用真实运行时能拿到真实的容器组件。做法与第三步对称在应用渲染ProfilePageContainer的入口处用ProfilePageContext.Provider把真实容器放入 Context。本主题关联的代码片段即此场景见 docs/_snippets/mock-context-container-provider.md。以 Next.js 为例这通常位于pages/profile.js。React 版本// pages/profile.js import React from react; import ProfilePageContext from ./ProfilePageContext; import { ProfilePageContainer } from ./ProfilePageContainer; import { UserPostsContainer } from ./UserPostsContainer; import { UserFriendsContainer } from ./UserFriendsContainer; // 确保你的 context 值在每次渲染之间保持引用相等referentially equal。 const context { UserPostsContainer, UserFriendsContainer, }; export const AppProfilePage () { return ( ProfilePageContext.Provider value{context} ProfilePageContainer / /ProfilePageContext.Provider ); };Solid 版本// pages/profile.js import ProfilePageContext from ./ProfilePageContext; import { ProfilePageContainer } from ./ProfilePageContainer; import { UserPostsContainer } from ./UserPostsContainer; import { UserFriendsContainer } from ./UserFriendsContainer; // 确保你的 context 值在每次渲染之间保持引用相等referentially equal。 const context { UserPostsContainer, UserFriendsContainer, }; export const AppProfilePage () { return ( ProfilePageContext.Provider value{context} ProfilePageContainer / /ProfilePageContext.Provider ); };关键细节context 值为何必须模块级定义片段中特别用注释强调了一行容易忽略的最佳实践// Ensure that your context value remains referentially equal between each render. const context { UserPostsContainer, UserFriendsContainer, };context对象被定义在组件函数之外模块作用域而不是AppProfilePage内部。原因在于 React/Solid 中 Context 的性能语义若在组件体内创建对象{ UserPostsContainer, UserFriendsContainer }每一次渲染都会产生一个新的对象引用Provider 的value引用一旦变化所有订阅该 Context 的消费者组件都会触发重渲染页面级 Provider 包裹着整棵子树这将导致每次父组件重渲染都连带整棵页面子树不必要的重渲染。把对象提升到模块顶层就能保证每次渲染之间引用相等referentially equal从而避免上述连锁重渲染。若某容器集合需要在渲染期内变动则应当用useMemoReact等机制在合适的依赖前提下缓存该值而不是随手内联。第五步可选全局容器 Context 与 preview 的 Decorator如果某些容器几乎出现在应用的每个页面上如NavigationContainer为其逐一在页面入口创建 Provider 会很啰嗦。官方建议另建一个全局容器 Context如GlobalContainerContext放到应用顶层只提供全局必需的容器具体做法参见 mock-context-container-global.md。在 Storybook 侧则在.storybook/preview.js中导出一个全局 Decorator把来自Navigation.stories.js的normalstory 作为NavigationContainer注入所有 Story// .storybook/preview.jsReactCSF 3 import * as React from react; import { normal as NavigationNormal } from ../components/Navigation.stories; import GlobalContainerContext from ../components/lib/GlobalContainerContext; const context { NavigationContainer: NavigationNormal, }; const AppDecorator (storyFn) { return ( GlobalContainerContext.Provider value{context}{storyFn()}/GlobalContainerContext.Provider ); }; export default { decorators: [AppDecorator] };TypeScript ReactCSF 3版本会额外标注组件类型// .storybook/preview.tsReactCSF 3 import * as React from react; // 将 your-framework 替换为你所用的框架如 react-vite、nextjs、nextjs-vite 等。 import type { Meta, StoryObj } from storybook/your-framework; import { normal as NavigationNormal } from ../components/Navigation.stories; import GlobalContainerContext from ../components/lib/GlobalContainerContext; const context { NavigationContainer: NavigationNormal, }; const AppDecorator (storyFn) { return ( GlobalContainerContext.Provider value{context}{storyFn()}/GlobalContainerContext.Provider ); }; const preview: Preview { decorators: [AppDecorator], }; export default preview;其中storybook/your-framework为文档中的占位写法实际项目中应替换为具体框架入口如storybook/react-vite、storybook/nextjs。使用实验性 CSF Next 语法时用definePreview包装相同的 Decorator 配置见 mock-context-container-global.md。与Mocking Providers路径的对比与取舍本模式本质上是对 Mocking providers 的一种结构性延伸。常规 Provider Mock如主题 Provider只需用 Decorator 包裹组件并伪造一个静态值若需要按 Story 差异化取值可借助 Decorator 的第二个context参数读取该 Story 的parameters从而一次定义、按 Story 调参见 mock-provider-in-preview.md 与 mocking-providers.mdx。而 Container Context 模式更进一步Context 中存放的不是值而是组件本身。它要解决的不是给组件喂一份假数据而是让页面在任意深度都能无痛地换掉数据与状态逻辑所在的那一层组件。二者相辅相成Container Context 负责把真实/虚假实现的选择权上移Provider Mock 负责在选定实现后控制其内部数据。小结这套模式带来的实际收益回到 docs/writing-stories/build-pages-with-storybook.mdx 的上下文Container/Context 模式的核心收益可归纳为页面级 Story 无需关心数据依赖——只需在 Story 中把容器替换为各自的故事即可渲染真实页面布局复用而非重写——Mock 版本大多直接取自子组件 Stories遵循 DRYStory 维护成本低深度嵌入无压力——容器以 Props/Context 传递无论在页面组件树多深处都可整体替换应用侧对称注入——真实入口pages/profile.js用 Provider 包住真实容器即可展示组件代码零改动配合 Decorator 进一步收敛——重复性 Provider 包裹可提升为 Story 级或全局级 Decorator。从 mock-context-create.mdContext 定义、mock-context-in-use.md展示组件消费、mock-context-container.mdStorybook 侧 Mock、mock-context-container-provider.md应用侧真实 Provider到 mock-context-container-global.md全局容器五段片段即构成该模式的最小可运行闭环可直接对照实现到自己的 React 或 Solid 项目中。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/10/30 1:40:45

跳点搜索JPS原理与实现:从A*到几十毫秒的栅格寻路优化

前阵子在做园区低速物流车的全局路径模块,甲方给过来的需求很直接:在几百米见方的栅格地图上,从库房门口到充电桩要能快速出一条不绕远、不震荡的路径,同时嵌入式板卡上的CPU预算不高,不能把核都吃满。最早用A*跑&…

2026/10/30 1:42:43

2026知网AIGC检测通关!AI率狂降至5%,亲测百试百灵

真的要崩溃了!熬夜改到凌晨三四点,眼睛都快睁不开,逐字逐句润色、语序换了又换,结果一检测还是满屏红。 别再傻傻硬改、纯手动死磕了!不仅浪费时间,还越改越乱、AI率越改越高。我花了整整一周,…

2026/10/30 1:35:53

剑网3宏脚本怎么选?JX3Toy全职业PVE Lua宏脚本使用指南

剑网3宏脚本怎么选?JX3Toy全职业PVE Lua宏脚本使用指南 【免费下载链接】JX3Toy 全功能减负工具 项目地址: https://gitcode.com/GitHub_Trending/jx/JX3Toy JX3Toy 是一个开源的剑网3全职业 PVE 宏脚本库,用 Lua 脚本按门派、按服务器版本分类存…

2026/10/29 17:05:52

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/29 17:06:02

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/29 17:06:13

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/30 0:00:46

NCP1342高频准谐振反激控制器:从设计约束到验收的完整方案

简介:NCP1342 高频准谐振反激控制器应用介绍译文文档,面向电源设计工程师、硬件研发人员及开关电源学习者,帮助读者跨越英文原版资料的语言门槛,系统理解这款高集成度 QR Flyback 控制器的设计要点。文档围绕芯片核心特性展开&…

2026/10/30 0:00:46

NCP1342准谐振反激电源设计:从选型到验收的全流程解析

简介:这份文档面向电源设计工程师与开关电源学习者,系统梳理NCP1342高频准谐振反激控制器的应用要点,帮助读者快速理解这款高集成度离线电源变换器控制芯片的工作机制与设计方法。内容涵盖芯片核心特性、引脚功能定义、电气特性参数表、最大额…

2026/10/30 0:00:46

Web系统自动登出机制设计与实现全解析

1. 系统自动登出机制的设计初衷在各类Web应用和后台管理系统中,自动登出功能早已成为标配。这个看似简单的功能背后,实际上涉及用户安全、资源管理和体验优化的多重考量。作为从业十多年的老开发,我见过太多因为登出逻辑设计不当导致的安全事…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑