尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

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),仅供参考
返回列表