ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Storybook Args 组合(Args Composition):在同一组件多故事间复用与继承参数的完整实践

Storybook Args 组合(Args Composition):在同一组件多故事间复用与继承参数的完整实践 Storybook Args 组合Args Composition在同一组件多故事间复用与继承参数的完整实践本篇以 Storybook 官方文档体系中的 Args 组合代码示例button-story-primary-composition.md为主体系统讲解如何利用 ES2015 对象展开object spread语法在同一组件的多个 story 之间复用args并延伸到跨 story 文件组合复合组件场景、CSF 3 与 CSF Next工厂函数两种写法的差异、以及.extend/.composed/.input等进阶 API。读完本文你将能够在 React、Vue 3、Angular、Svelte、Web Components 等多种渲染器下用可维护、可追踪的方式编写互相关联的组件故事避免逐条复制粘贴参数。从一个典型需求说起Primary 与 Secondary为同一个组件编写多个 story 时最自然的场景是“变体复用”Primary是主按钮Secondary是次按钮两者几乎只有primary这一个布尔值不同。若把两份参数完整抄写两遍后续每新增一个属性就要同步修改多处。Storybook 给出的做法是把Primary的args作为基座用对象展开语法组合出新的 storyexport const Secondary { args: { ...Primary.args, primary: false, }, };这就是本节要深入讲解的Args 组合Args composition。Storybook 官方在 writing-stories/args.mdx 中将其定义为把一个 story 的参数拆分出来再组合进其他 story。它只发生在 JavaScript 对象层不依赖任何 Storybook 内部机制因此也不需要修改组件本身。先回顾 Args 的三个作用层级在进入组合细节前需要先明确args可以定义在哪一层以及它们的覆盖优先级。据 args.mdx 所述args是一个可被 JSON 序列化的对象由字符串键和对应的合法值类型组成可以被传入组件的 props、slots、styles、inputs 等取决于所用框架。Story 级Story args写在具体 story 对象CSF 3 的export const Xxx、CSF Next 的meta.story({...})或 Svelte CSF 的Story组件上只对该 story 生效。组件级Component args写在 meta 的默认导出或defineMeta/preview.meta上作用于该组件全部 story直到被某个 story 覆盖。全局级Global args写在.storybook/preview.*的默认导出上作用于所有组件的所有 story。官方同时提醒多数“全局性设置”如主题其实更适合用 globals 工具栏完成而非全局 args。组合composition发生在 story 层对象之间而组件级与全局级 args 则为大多数 story 共享的默认值提供了更省力的替代方案详见后文 组件级 args 与组合的取舍。同组件多 story 的组合写法全框架示例本仓库的代码片段 button-story-primary-composition.md 集中展示了这一模式在Angular、Reactcommon、Svelte、Vue 3、Web Components等渲染器以及CSF 3、CSF Next预览、Svelte CSF三种写法下的完整形态。以下按写法组织代码可直接对应到你的技术栈。CSF 3TypeScript 写法React / Angular 通用结构import type { Meta, StoryObj } from storybook/your-framework; import { Button } from ./Button; const meta { component: Button, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Primary: Story { args: { primary: true, label: Button, }, }; export const Secondary: Story { args: { ...Primary.args, primary: false, }, };Angular 框架的差异仅在于导入对象组件以具名类形式导入import { Button } from ./button.component类型参数直接使用StoryObjButtonmeta 声明同样以MetaButton给出组合逻辑一字不差import type { Meta, StoryObj } from storybook/angular; import { Button } from ./button.component; const meta: MetaButton { component: Button, }; export default meta; type Story StoryObjButton; export const Primary: Story { args: { primary: true, label: Button, }, }; export const Secondary: Story { args: { ...Primary.args, primary: false, }, };CSF 3JavaScript 写法React / 通用渲染器不启用satisfies类型断言与StoryObj泛型时JS 版本更加直白——meta 直接作为默认导出story 即普通对象import { Button } from ./Button; export default { component: Button, }; export const Primary { args: { primary: true, label: Button, }, }; export const Secondary { args: { ...Primary.args, primary: false, }, };Svelte原生 Svelte CSF.stories.svelteSvelte CSF 走的是storybook/addon-svelte-csf的defineMetaStory组件路线。组合既可以在script module里先把共享参数声明为常量再通过属性展开传给Storyscript module import { defineMeta } from storybook/addon-svelte-csf; import Button from ./Button.svelte; const { Story } defineMeta({ component: Button, }); const primaryArgs { primary: true, label: Button, } /script Story namePrimary args{primaryArgs} / Story nameSecondary args{{...primaryArgs, primary: false}} /上为 JSTypeScript 版本仅将script module标签替换为script module langts其余相同。Svelte CSF 也保留 CSF 3 对象式写法meta 用satisfies Metatypeof ButtonSvelte 组件默认导出Primary、Secondary以标准 CSF 3 story 对象导出并同样复用...Primary.argsimport type { Meta, StoryObj } from storybook/your-framework; import Button from ./Button.svelte; const meta { component: Button, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Primary: Story { args: { primary: true, label: Button, }, }; export const Secondary: Story { args: { ...Primary.args, primary: false, }, };Web Components组件标识即自定义元素名Web Components 渲染器不导入组件类而是通过注册的自定义元素名如demo-button引用组件。JS 版本export default { component: demo-button, }; export const Primary { args: { primary: true, label: Button, }, }; export const Secondary { args: { ...Primary.args, primary: false, }, };TypeScript 版本引入StorybookObj类型但不绑定组件泛型component仍是元素名字符串import type { Meta, StoryObj } from storybook/web-components-vite; const meta: Meta { component: demo-button, }; export default meta; type Story StoryObj; export const Primary: Story { args: { primary: true, label: Button, }, }; export const Secondary: Story { args: { ...Primary.args, primary: false, }, };CSF NextPreview从 preview.meta 的工厂函数派生CSF Next 是 Storybook 对 CSF 的下一代演进仓库中的 csf-next.mdx 有完整参考。它采用工厂函数链definePreview.storybook/preview.*→preview.meta→meta.story每一环都带完整类型推导。story 文件不再需要 default export 的 meta而是从 preview 构造出带类型的 metaimport preview from ../.storybook/preview; import { Button } from ./Button; const meta preview.meta({ component: Button, }); export const Primary meta.story({ args: { primary: true, label: Button, }, }); export const Secondary meta.story({ args: { ...Primary.input.args, primary: false, }, });注意此处组合的来源从Primary.args变成了Primary.input.args。原因在于 CSF Next 中Primary不再是裸 args 对象而是工厂函数的产物一个带.input、.composed、.run()等能力的 Story 对象.input指向你直接传给meta.story()的原始配置其中包含args。若沿用 CSF 3 直觉去访问Primary.args在 CSF Next 中已属被弃用的用法。上面是 React 与 Web Components 的 TS/JS 通用形态Vue 3 仅需把组件导入换成import Button from ./Button.vue。仓库代码片段还专门保留了 JS 版本注释JS snippets still needed while providing both CSF 3 Next同时提供 CSF 3 与 CSF Next 两份文档期间 JS 片段仍被需要说明 CSF Next 在文档层面同时覆盖 JS/TS 两类项目。组合的语义展开顺序即覆盖顺序...Primary.args是 ECMAScript 2015 的对象展开语法。Secondary.args求值顺序为先展开Primary.args的全部键值primary: true,label: Button再写入primary: false覆盖同名键。因此最终Secondary.args { primary: false, label: Button }。从源码结构推断Storybook 渲染 story 时会执行多层 args 合并全局 args → 组件级 args → story 级 args同一组件内 story 间的展开复用只是这层合并开始前、纯对象层的一步——后出现的键总是赢。这意味着组合时务必把继承来源写在展开位置、把差异化覆盖写在后面顺序颠倒如{ primary: false, ...Primary.args }会让primary: true反噬覆盖得到与预期相反的结果。同样的覆盖语义在 URL 参数中也成立据 args.mdx通过?path/story/xxx--defaultargskey:value指定的 args 会扩展并覆盖story 上设置的任何默认值出于 XSS 防护URL 中的 arg 键值只允许字母数字、空格、下划线与连字符复杂值需要交给 Controls 面板或mappingargTypes.mapping可以把简单字符串映射为 JSX 等不可序列化类型键对应 arg 的取值而非options下标。组件级 args 与组合的取舍官方在 Args 组合章节后给出了一条实践建议同样收录于 args.mdx如果你发现自己为某个组件的大多数 story 反复复用同一组 args应考虑改用组件级 args。组件级 args 的写法是把共享参数提进 meta代码片段 button-story-component-args-primary.mdimport type { Meta } from storybook/your-framework; import { Button } from ./Button; const meta { component: Button, argTypes: { backgroundColor: { control: color }, }, args: { // 现在所有 Button stories 默认都是 primary primary: true, }, } satisfies Metatypeof Button; export default meta;这样Primary、Secondary乃至以后新增的所有 story 都自动继承primary: true需要差异化时在对应 story 的 args 里用展开或直接写键覆盖即可。取舍建议可归纳为少数变体之间的细微差异用展开组合贯穿大多数/全部 story 的默认值上提为组件级 args。CSF Next 与 Svelte CSF 同样支持组件级 argsdefineMeta/preview.meta的args键形态一致。跨 story 文件组合面向复合组件的组合Args 组合并不限于同一个文件、同一个组件。当一个复合组件把参数原样透传给子组件时官方推荐直接组合子组件 story 的参数。args.mdx 中展示了用 Page 组合 Header 故事的例子完整实现见代码片段 page-story.mdimport { Page } from ./Page; // 导入 Header 的全部 stories import * as HeaderStories from ./Header.stories; export default { component: Page, }; export const LoggedIn { args: { ...HeaderStories.LoggedIn.args, }, };这一形态在仓库片段中覆盖了 Angular额外配moduleMetadata声明子组件、React、Vue 3配合 render 函数模板、Web Components、Svelte 等渲染器。它解决了很实际的维护问题Header 的已登录态参数若散落在多个 Page story 里改一处接口就要同步多处而...HeaderStories.LoggedIn.args让 Page 的 story 始终与 Header story 保持同源。组合子组件的 stories 时还常把默认导出整个导入import * as HeaderStories以命名空间方式一次性拿到该文件全部 story避免逐个具名 import。CSF Next 下的更优解.extend与composedCSF Next 为基于某 story 派生出新 story提供了专用方法Story.extend。据 csf-next.mdx 的说明extend的合并是智能合并按属性类型区分规则args浅合并shallow merge即对象展开语义parameters深合并但数组整体替换decorators与tags拼接concat。const meta preview.meta({ component: Button }); export const Primary meta.story({ args: { primary: true, label: Button, }, }); export const PrimaryDisabled Primary.extend({ args: { disabled: true, }, });同时CSF Next 把所有由 story、组件 meta、preview 配置共同组合而成的最终属性收纳进Story.composedStory.composed.args、Story.composed.parameters…composed 这个名字正是因为其中值是三者合成而来若要访问直接输入则用Story.input。对应地CSF 3 中Story.args式的直接访问在 CSF Next 中虽仍兼容但已被弃用——当你要组合某个 CSF Next story 时读取Primary.input.args本例写法或Primary.composed.args需要合并后的完整值而不是旧习惯的Primary.args。值得对照的是仓库配套的自动迁移工具链CSF 3 → CSF Next 的 codemod 实现位于 code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts并由 csf-factories.ts 登记为迁移命令相关辅助函数如把默认导出 meta 转成preview.meta(...)调用的 config 转换器在 config-to-csf-factory.ts 及其测试中。若想在自己项目里自动化升级可以在升级到最新 Storybook 后运行对应的 CSF factories 迁移命令完成全量改写再人工确认组合处由.args到.input.args的语义切换。此外仓库 docgen 测试夹具如 csf4/preview.ts中也出现了definePreview形态可作为 CSF Next 项目.storybook/preview.ts的参考模板。组合的可移植性测试与运行时复用Args 组合产生的story 即数据特性也让组合结果可以直接迁移到测试场景。仓库的 portable stories 相关片段如 portable-stories-vitest-compose-story.md展示在 Vitest 中组合 story 后可通过Primary.run({ args: { ...Primary.args, label: Hello world } })以展开语法在运行时临时覆盖被测 story 的参数再执行渲染无需改动源 story。这印证了 Args 组合的通用心智模型——展开复用默认值、后置键覆盖差异——在 Storybook UI、portable stories 与自动化测试里保持一致。小结与最佳实践清单Args 组合是 Storybook 里成本最低、收益最直接的参数复用手段。基于上文与仓库文档、片段可将实践要点收敛如下同一组件的变体用 ES2015 展开组合args: { ...Base.args, differKey: value }覆盖键置于展开之后保持顺序语义正确。大多数 story 共享的默认值上提为组件级 argsmeta 或preview.meta的argsstory 级只写差异。全局性主题类设置优先考虑 globals 而非全局 args。复合组件直接组合子组件 story 文件import * as XxxStories...XxxStories.State.args保持参数同源。CSF Next 项目用Primary.input.args直接输入或Primary.composed.args合成结果取参纯派生关系优先使用Story.extend并注意其按属性类型区分的合并规则。Svelte CSF可在script module中先声明共享参数常量再于Story args{...}中展开。组合得到的对象是纯数据可同样用于 Vitest/portable stories 的运行时参数覆盖。关联资料args.mdxArgs 完整指南、csf-next.mdxCSF Next 参考与迁移、button-story-component-args-primary.md组件级 args、page-story.md跨文件组合。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表