Storybook 通过 Parameters 驱动 Addon 的跨框架实践:从 `myAddon` 数据传递到 `selectStory` 编程式选中

发布时间:2026/9/18 2:31:16

Storybook 通过 Parameters 驱动 Addon 的跨框架实践:从 `myAddon` 数据传递到 `selectStory` 编程式选中 Storybook 通过 Parameters 驱动 Addon 的跨框架实践从myAddon数据传递到selectStory编程式选中导读Storybook 的parameters是组件、故事与 addon 之间传递配置的标准通道。本文以仓库中 docs/_snippets/button-story-with-addon-example.md 这份被官方文档 Addon API 引用的Button myAddon示例为骨架逐一剖析 CSF 3、CSF Next 实验性与 Svelte CSF 下如何声明带 addon 参数的故事并结合storybook/manager-api中useParameter、selectStory的实现说明 addon 如何读回参数、如何编程式跳转到对应故事。读完本文你将能够在任意受支持框架中写出参数可被 addon 消费的故事文件并掌握其底层索引与导航原理。一、这段示例在官方文档中的定位button-story-with-addon-example.md位于仓库的代码片段库docs/_snippets/下被 docs/addons/addons-api.mdx 的api.selectStory()小节引用。该页面在讲解如何用 Storybook API 选中单个故事时先用这个示例交代被选中的故事长什么样故事文件Button.stories.*中通过parameters把自定义数据传给某个 addonmyAddon随后页面给出配套的 storybook-addons-api-selectstory.md演示在 addon 的 manager 代码里调用api.selectStory(Button, ...)进行跳转。也就是说这组示例构成了一个完整的闭环story 侧声明 addon 配置parameters→ addon 侧读取配置 → 通过 Storybook API 定位并切换到该 story。它是学习story 与 addon 如何交互的最小可用样例。二、机制基础parameters是组件与 addon 的配置通道2.1 什么是 parameters按官方文档 parameters.mdx 的定义parameters是一组关于 story 的静态、命名元数据通常用于控制 Storybook 功能与 addon 的行为例如用parameters.backgrounds配置背景工具栏的选项。它有三个声明层级Story 级写在某个 story 导出对象或 Svelte CSF 的Story的parameters键上Component 级写在 CSF 默认导出meta上作用于该组件全部故事Global 级写在.storybook/preview.ts的parameters导出上作用于所有故事。2.2 合并规则addon 作者必须理解继承遵循两条规则越具体优先级越高story 参数覆盖 component 参数component 参数覆盖 global 参数参数是合并merge而非替换键只可能被覆写绝不会被丢弃。这意味着 addon 可以让用户在 global 层给出默认配置再允许单个 story 覆写某个子键。文档特别提醒如果你要设计一个依赖 parameters 的 API例如一个 addon务必把这种合并行为纳入考量。因此示例中把整个 addon 配置收敛在一个以 addon 命名的命名空间键如myAddon之下正是避免键冲突、方便按层级覆写的常见做法。2.3 addon 侧如何读回参数在 manager 侧addon 的 UI 代码中通过 storybook-addons-api-useparameter.md 展示的useParameterhook 读取当前 story 的参数import React from react; import { AddonPanel } from storybook/internal/components; import { useParameter } from storybook/manager-api; export const Panel () { // 读取当前故事上名为 custom-parameter 的参数未定义时回退到第二个参数 const value useParameter(custom-parameter, initial value); return ( AddonPanel keycustom-panel activetrue {value initial value ? ( h2The story doesnt contain custom parameters. Defaulting to the initial value./h2 ) : ( h2Youve set {value} as the parameter./h2 )} /AddonPanel ); };针对本示例中的结构addon 读取的是myAddon这个命名空间即useParameter(myAddon)会返回形如{ data: this data is passed to the addon }的对象。同理若采用官方风格装饰器makeDecorator要求传入parameterName与 addon 同名当某个 story 声明了{ exampleParameter: { disable: true } }其中exampleParameter即该 addon 的parameterName时其装饰器将不会被调用——这正是story 通过参数开关控制 addon 行为的又一体现相关说明见 addons-api.mdx 的makeDecorator小节。三、示例骨架拆解一份带 addon 参数的 Button 故事所有框架变体共享同样的信息结构以 CSF 3 TypeScript 为例import type { Meta, StoryObj } from storybook/your-framework; import { Button } from ./Button; const meta { // title 是可选属性不写时 Storybook 会根据文件位置/组件自动生成标题 title: Button, component: Button, // 在 metacomponent级声明本组件所有 story 都会拿到这份 addon 参数 parameters: { myAddon: { data: This data is passed to the addon, }, }, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; // render 函数是框架相关的特性允许你精确控制组件如何渲染 export const Basic: Story { render: () ButtonHello/Button, };要点拆解title可选注释明确指出title 用于手动指定故事在侧边栏的层级标题省略时 Storybook 依据组件与文件路径自动生成标题参见 parameters.mdx 对故事组织方式的说明。同时这里的title正是后续api.selectStory(Button, ...)第一个参数所匹配的kind。component: Button将 meta 关联到被测组件供自动文档、Props 表格与类型推导使用。parameters.myAddon.data命名空间化的自定义参数——这是本示例的核心。它位于 meta 上因此组件内所有故事共享任何 addon 都可以通过useParameter(myAddon)读回。Basic故事 render函数故事导出名Basic会成为其 story id 的组成部分见第五节。render是框架级特性用来决定最终渲染什么在示例中它以编程方式渲染一个按钮。四、跨框架完整实现对照下面按 CSF 3、CSF Next、Svelte CSF 三种写法还原button-story-with-addon-example.md中参数完全一致、仅渲染层不同的全框架示例。4.1 CSF 3AngularTypeScriptimport type { Meta, StoryObj } from storybook/angular; import { Button } from ./button.component; const meta: MetaButton { title: Button, component: Button, parameters: { myAddon: { data: this data is passed to the addon, }, }, }; export default meta; type Story StoryObjButton; export const Basic: Story { render: () ({ template: app-buttonhello/app-button, }), };Angular 的 render 返回一个模板对象指明使用哪个组件的template渲染内容。4.2 CSF 3ReactJavaScript 与 TypeScript 两种写法JavaScriptButton.stories.js|jsx直接使用对象默认导出与命名导出import * as React from react; import { Button } from ./Button; export default { title: Button, component: Button, parameters: { myAddon: { data: This data is passed to the addon, }, }, }; export const Basic { render: () ButtonHello/Button, };TypeScriptButton.stories.ts|tsx则推荐用satisfies Metatypeof Button获取精确类型storybook/your-framework需替换为实际框架包如react-vite、nextjs、nextjs-vite等import * as React from react; // 将 your-framework 替换为你实际使用的框架包例如 storybook/react-vite import type { Meta, StoryObj } from storybook/your-framework; import { Button } from ./Button; const meta { title: Button, component: Button, parameters: { myAddon: { data: This data is passed to the addon, }, }, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Basic: Story { render: () ButtonHello/Button, };4.3 CSF 3SolidTypeScriptimport type { Meta, StoryObj } from storybook-solidjs-vite; import { Button } from ./Button; const meta { title: Button, component: Button, parameters: { myAddon: { data: this data is passed to the addon, }, }, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Basic: Story { render: () ButtonHello/Button, };Solid 的 JS 版与之完全相同仅去掉类型标注与satisfies可直接用普通对象默认导出。4.4 CSF 3Vue 3TypeScriptimport type { Meta, StoryObj } from storybook/vue3-vite; import Button from ./Button.vue; const meta { title: Button, component: Button, parameters: { myAddon: { data: this data is passed to the addon, }, }, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Basic: Story { render: () ({ components: { Button }, template: Button labelHello /, }), };Vue 的 JS 变体Button.stories.js结构一致默认导出普通对象Basic的render同样返回{ components, template }。4.5 CSF 3Web ComponentsTypeScriptimport type { Meta, StoryObj } from storybook/web-components-vite; import { html } from lit; const meta: Meta { title: Button, component: custom-button, parameters: { myAddon: { data: this data is passed to the addon, }, }, }; export default meta; type Story StoryObj; export const Basic: Story { render: () htmlcustom-button labelHello/custom-button, };注意此时component是自定义元素名custom-button而非类render借助lit的html标签模板返回元素。JS 变体Button.stories.js同样以custom-button作为 component、html模板渲染只是不写类型。4.6 CSF 3SvelteSvelte 的 CSF 3 通常让component story 参数自动映射到组件 props因此示例中Basic是空对象即可Button.svelte的默认渲染即可完成工作。TypeScript 写法// 将 your-framework 替换为 svelte-vite 或 sveltekit import type { Meta, StoryObj } from storybook/your-framework; import Button from ./Button.svelte; const meta { title: Button, component: Button, parameters: { myAddon: { data: this data is passed to the addon, }, }, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Basic: Story {};五、CSF Next 实验性preview.meta/meta.story仓库示例同时给出了新实验性写法CSF Next不再通过export default声明 meta而是先从../.storybook/preview导入 preview 实例用preview.meta({ ... })创建 meta再用meta.story({ ... })声明每个故事。title、component、parameters、render的语义与 CSF 3 完全一致只是载体从模块默认导出变为 preview 派生对象。以 React 为例import * as React from react; import preview from ../.storybook/preview; import { Button } from ./Button; const meta preview.meta({ title: Button, component: Button, parameters: { myAddon: { data: This data is passed to the addon, }, }, }); export const Basic meta.story({ render: () ButtonHello/Button, });Angular 变体Button.stories.ts与之对称仅渲染层改为返回 template 对象import preview from ../.storybook/preview; import { Button } from ./button.component; const meta preview.meta({ title: Button, component: Button, parameters: { myAddon: { data: this data is passed to the addon, }, }, }); export const Basic meta.story({ render: () ({ template: app-buttonhello/app-button, }), });Vue 与 Web Components 的 CSF Next 变体也遵循同一模式meta 与故事对象均来自preview.meta(...)/meta.story(...)唯一的区别是 render 部分——Vue 返回{ components: { Button }, template: Button labelHello / }Web Components 用html\且component: custom-button。这证实CSF Next 只是声明语法的变化parameters 的命名空间约定与 addon 通信协议没有改变addon 作者无需为两种语法分别适配读取逻辑。六、Svelte CSF.stories.svelte与defineMetaSvelte 官方还支持在.svelte故事文件中书写 CSF依赖storybook/addon-svelte-csf。示例以script moduledefineMeta声明 meta 与 parameters用Story nameBasic /声明故事script module import { defineMeta } from storybook/addon-svelte-csf; import Button from ./Button.svelte; const { Story } defineMeta({ title: Button, component: Button, parameters: { myAddon: { data: this data is passed to the addon, }, }, }); /script Story nameBasic /与 CSF 3/CSF Next 形成呼应同一份parameters.myAddon配置在 Svelte CSF 中通过defineMeta声明nameBasic则等价于导出名为Basic的 story——这意味着第五节selectStory(Button, Basic)的定位方式对它同样适用。七、闭环最后一环用api.selectStory编程式选中这个故事7.1 使用方式在 addon 的 manager 入口my-addon/src/manager.js|ts中注册插件后即可拿到 Storybook API 并调用selectStoryaddons.register(my-organisation/my-addon, (api) { api.selectStory(Button, Default); });selectStory接受两个参数story 的 kind/title如上面的Button对应示例中 meta 的title: Button和可选的 story 名。需要说明的是name必须与 CSF 中的实际导出名或 Svelte CSF 的Story name严格一致。若你的故事导出名为Basic则应调用api.selectStory(Button, Basic)仓库配套片段 storybook-addons-api-selectstory.md 中出现的Default来自早期版本示例命名使用时请以自身故事文件为准。更精确的做法是直接传完整 story id见 7.2。7.2 底层实现索引查找与导航selectStory实现在 code/core/src/manager-api/modules/stories.ts其签名见同文件 L143-L147为selectStory: (kindOrId?: string, story?: StoryId, obj?: { ref?: string; viewMode?: API_ViewMode; scrollTo?: string }) void;对照源码可以归纳出完整的分支语义只传 id/kind先在当前索引hash中按titleOrId、sanitize(titleOrId)查找条目若命中的是 component/group非 story/docs则调用findLeafEntry找到其第一个子故事实现点目录跳首个故事只传 name表示在当前组件kind内按名跳转会拼出toId(kindSlug, name)查找同时传 kind 与 name拼出完整 story idtoId(titleOrId, name)如button--basic直接定位若未命中会走 legacy 兼容逻辑——把titleOrId当组件处理在其 children 里按 name 匹配ref选项用于组合式 Storybookrefs跳转目标会加refId前缀本示例为本地故事无需传scrollTo选项最终导航会生成/story/button--basic#scrollTo形式的 URL 并调用navigateWithQueryParams完成页面切换。由此可以看到第五节示例之所以能作为selectStory的演示对象正是因为该 Button 故事的 story id 由title: Button与导出名共同推导而来。仓库测试 code/core/src/manager-api/tests/stories.test.ts 中大量断言如api.selectStory(a--2)、api.selectStory(a, 2)、api.selectStory(undefined, 2)也逐一验证了上述 id 定位、组件内按名跳转与 kindname 组合这三种调用形态。7.3 相关 APIselectInCurrentKind与 URL 状态文档 addons-api.mdx 还补充了同类 APIapi.selectInCurrentKind(storyName)与selectStory类似但只接受 story 名一个参数在当前组件内切换api.getUrlState(overrideParams)读取应用 URL 状态含覆写后的参数值可用于向 URL 同步 addon 的临时状态api.getCurrentStoryData()返回当前 story 的完整数据id、kind、name 与 parameters——这是 addon 拿到当前故事参数的另一种程序化途径。八、实践要点小结命名空间先行addon 自定义参数应集中放在以 addon 命名的键下如parameters.myAddon利用全局/组件/故事三层合并规则避免键冲突并支持按故事覆写。meta 级参数覆盖面广把parameters.myAddon写在 meta 上组件下所有故事自动携带若需按故事差异化则在 story 导出上加同名子键即可覆盖。框架差异仅在渲染层Angular 返回 template 对象、Vue 返回{ components, template }、Web Components 用lit的html、Svelte 默认渲染可留空对象、React/Solid 直接返回 JSX——parameters 声明方式在 CSF 3 / CSF Next / Svelte CSF 与所有渲染器中保持一致。编程式跳转务必对齐命名api.selectStory(kind, name)的name必须等于 CSF 导出名或Story name不确定时优先使用完整 story id如button--basic更稳妥。底层查找与导航逻辑可参考 stories.ts 及对应单元测试。如需进一步阅读可回到本示例的引用出处 docs/addons/addons-api.mdx 查看useParameter、useAddonState、getUrlState等整套 Addon API或阅读 docs/writing-stories/parameters.mdx 掌握参数继承的完整规则。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/18 2:31:16

测量平差入门:水准网闭合差处理与最小二乘精度评定

咱们搞测量的,最怕的不是仪器不好用,而是数据测回来之后自己都说不清楚问题出在哪。你辛辛苦苦测完一段水准,闭合差超了限,监理问你怎么处理,你说“重测”,理论上没问题,但成本谁出?…

2026/9/18 2:26:15

Codex 查 React Effect 时接口报 401?TaoToken 这样改 Base URL

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 2:26:15

人工势场法路径规划三大缺陷及ROS+Gazebo仿真改进实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 3:46:18

Python视觉识别项目实战:从猫狗识别到模型部署

1. 项目定位:Python视觉识别到底在做什么先聊点实在的。我在社区和群里经常看到有人问"Python学完之后能干什么",或者更直接一点,"人工智能大作业选什么方向好"。我的看法一直很明确:视觉识别是Python进阶路线…

2026/9/18 3:46:18

HAProxy负载均衡实战:从配置到故障转移的完整实验指南

说实话,我第一次看到“HAProxy实验”这个标题时,就想起当年自己搭第一个负载均衡集群时手忙脚乱的样子。那时候连四层和七层都分不清,配置写错了就在那一个劲地重启服务,日志又没开,排查了半天才发现是后端健康检查路径…

2026/9/18 3:46:18

Python排序算法全攻略:从冒泡到Timsort的工程实践

排序算法这个老生常谈的话题,几乎所有学Python的人都会碰到。面试要考,日常写业务代码要处理榜单、排行榜、数据分析前的预处理也绕不开。我在带新人时最常被问到的就是:网上讲排序的教程这么多,背哪个?用哪个&#xf…

2026/9/18 3:46:18

AI芯片设计从入门到进阶:避开放弃陷阱的系统学习路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 3:41:18

异常处理与排查:从编译期异常到运行时异常的完整指南

1. 从一条报错说起:异常到底是什么你有没有发现,"异常"这个关键词能挂出一长串热搜词:java 异常、python 异常怎么写、数组越界异常、编译期异常、windows 无法加载设备驱动程序 代码 31、我们的系统检测到您的计算机网络中存在异常…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/18 0:01:09

Google Colab 实战:运行模型、数据加载与报错排查

1. 为什么我劝你先搞懂 Colab 的运行模型1.1 Colab 到底是什么,跟本地跑代码差在哪Google Colab 简单说就是一台跑在浏览器里的 Linux 虚拟机,你打开一个 Notebook,背后就连上了一台带 GPU 的远程机器。你在单元格里敲的每一行 Python&#x…

2026/9/18 0:01:09

C语言数据类型与表达式详解

1. C语言数据与数据类型概述在C语言编程中,数据是程序处理的核心对象。理解数据的分类和特性是掌握C语言的基础。C语言中的数据主要分为四大类:常量、变量、表达式和函数。这些数据类型构成了C语言程序的基本元素,每种类型都有其独特的特性和…

2026/9/18 0:01:09

SQL时间字段指定时间段查询:区间语义、索引与时区避坑

上周排查一个线上问题&#xff0c;用户反馈"昨天的订单一条都没查到"&#xff0c;但数据库里明明躺着两千多条。最后定位下来&#xff0c;不是数据丢了&#xff0c;也不是接口挂了&#xff0c;而是那个查询条件把时间段写成了> 2024-05-20 00:00:00 AND < 2024…

2026/9/16 22:55:57

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/16 22:56:09

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/16 22:56:16

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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