ARTICLE DETAIL

资讯详情

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

Storybook Args 完整指南:用一个对象驱动多框架组件故事

Storybook Args 完整指南:用一个对象驱动多框架组件故事 Storybook Args 完整指南用一个对象驱动多框架组件故事Storybook Args 是「写故事」的核心你用一个普通的 JS 对象描述组件此刻该长什么样Storybook 就把它喂给组件的 props / 插槽 / 输入并让 Controls 面板、URL 参数、Actions 回调这些能力全部白送。整篇只围绕一件事讲透——同一个args对象如何在 React、Vue、Svelte、HTML 等框架里各归其位又被怎样一层层合并、覆盖、编辑。先记住一张结果图左侧选中Button的Primary故事预览区渲染出带 primary 状态的按钮底部 Controls 里primary开关、label文本、size等都能实时改、实时重渲染。不改组件源码驱动 Button 的每一种状态很多团队的第一反应是想改按钮的文案、颜色、尺寸就去改Button组件源码。Storybook 的思路相反——组件源码一行不动用一个args对象在故事这一层去驱动它。这里有个反直觉但很关键的事实args这个术语是 Storybook 给各框架组件输入起的统一叫法。React 的props、Vue 的props、Angular 的Input、Svelte 的 props在故事文件里统统写作args。所以下面跨框架的写法看着各不相同但args: { primary: true, label: Button }这个对象本身永远长一样。meta / story / args 三者到底是什么关系一个 stories 文件通常叫Button.stories.ts里只有两类东西一个默认导出和若干个具名导出。把它们拆开看关系一下就清楚了。角色写在回答的问题类比meta默认导出export default这个组件叫什么、放哪个侧边栏分组、默认装饰器是什么节目单args可挂在 meta / story / preview 任一层这一次要传哪些输入、取什么值演员手里的台词卡story具名导出export const Primary某一组具体参数下的一个状态节目单里的某一幕一句话meta描述组件story描述状态args是描述状态的那串参数。文件跟被测组件放同一目录只服务于开发期不会进生产构建见 docs/writing-stories/index.mdx。三步写出第一个带 args 的故事以 React 为例最小可运行版本只有十几行import { Button } from ./Button; export default { component: Button, // 侧边栏标题、Controls 的 argTypes 都从这里推导 }; export const Primary { args: { primary: true, // 这一行决定了按钮是 primary 态 label: Button, }, };为什么这么简单就够了React 渲染器默认会帮你把args逐个展开成Button {...args} /你根本不用写render。上 TS 时真正值钱的不是 import而是两处类型桥接import type { Meta, StoryObj } from storybook/react; import { Button } from ./Button; const meta { component: Button, } satisfies Metatypeof Button; // 约束 meta 本身写对了 export default meta; type Story StoryObjtypeof meta; // 让 args 基于 Button 真实 props 做补全 export const Primary: Story { args: { primary: true, label: Button }, };meta用satisfies而不是: Meta...是为了保留字面量类型Story StoryObjtypeof meta再把类型接到每个故事上于是args.label写错、primary传成字符串都会当场报红。跨框架 story 写法速查把八种框架的差异点压成一张表比逐段贴代码高效得多框架默认导出怎么写要不要手写 renderargs 落到哪React / Preact / Solidcomponent: 组件否自动透传propsVue 3component: 组件要v-bind透传propsAngularcomponent: 组件否Input()HTMLtitle: Button无组件模块要手搓 DOM你在 render 里自己用Svelte标准 CSFcomponent: 组件否propsSvelte CSFdefineMeta({...})否props / 插槽Web Componentscomponent: 元素名否attribute / propertyVue 之所以必须手写render是因为它要把args用v-bind绑进模板这一步最能体现「args → props」的映射机制import Button from ./Button.vue; export default { component: Button }; export const Primary { render: (args) ({ components: { Button }, setup() { return { args }; }, template: Button v-bindargs /, // args 在这里被拆成组件 props }), args: { primary: true, label: Button }, };HTML 渲染器没有框架运行时得自己把args组装成真实 DOM 节点export default { title: Button }; export const Primary { render: (args) { const btn document.createElement(button); btn.innerText args.label; // 消费 args.label const mode args.primary ? storybook-button--primary : storybook-button--secondary; btn.className [storybook-button, mode].join( ); return btn; // 只要 render 里用了 argsControls 就照样生效 }, args: { primary: true, label: Button }, };Angular 的差别在类型写法MetaButton直接把组件类当类型参数省掉了typeof那一绕Web Components 则因为拿的是一个字符串元素名没法参与推导TS 里StoryObj退化成不带泛型的宽泛写法args的约束让位给元素自身的 attribute 定义。三层 args 谁覆盖谁prepareStory 的合并顺序args可以在三个层级出现优先级从低到高是preview.* 默认导出 (global args) ← 影响所有组件的所有故事 ▲ 被覆盖 meta 默认导出的 args (component) ← 影响该组件的所有故事 ▲ 被覆盖 具名导出的 args (story) ← 只影响当前故事优先级最高这段后写的覆盖先写的规则在源码里有直接证据。故事准备阶段 code/core/src/preview-api/modules/store/csf/prepareStory.ts 里这样展开合并const passedArgs: Args { ...projectAnnotations.args, // global ...componentAnnotations.args, // component ...storyAnnotations?.args, // story放最后所以优先级最高 } as Args;也就是说合并发生在故事准备期跟组件自己的 props 声明完全解耦——这正是「写故事不改组件」能成立的底层原因。合并出的initialArgs还会继续走一遍argsEnhancers流水线同文件往下几十行比如从argTypes推导默认值就注入在这里。顺带一句如果你要的是全局统一主题这类需求globals 往往比 global args 更合适因为 globals 能在工具栏里直接切换。四个高频技巧复用、URL 覆盖、mapping 与 useArgs1. 对象展开复用。故事级 args 默认只影响自己但它是普通对象可以用展开运算符抄作业export const PrimaryLongName: Story { args: { ...Primary.args, // 先抄一份 Primary label: Button 长文本场景, // 再覆盖差异项 }, };同一组件的大多数故事共享一组 args 时更推荐把它上提到 component args面对由多个子组件拼成的复合组件则可以在 docs/writing-stories/args.mdx 的 Args composition 一节按子故事组合参数。2. 用 URL 直接盖 args。把args写进 query就能分享一个定格的 Controls 状态形如?path/story/avatar--defaultargsstyle:rounded;size:100。解析规则有几条硬约束别记错永远是key: value集合用分号;分隔值会被强转成对应argTypes类型支持对象与数组。null/undefined要加!前缀如nil:!null下标可写arr[0]:one。日期编码为!date(value)颜色为!hex(value)、!rgba(value)、!hsla(value)且 rgb(a)/hsl(a) 里不能有空格和百分号。出于 XSS 防护URL 里 args 的键值只允许字母、数字、空格、下划线、连字符其它类型会被忽略并从 URL 移除但 Controls 面板和argTypes.mapping仍可用。3. 用argTypes.mapping映射复杂值。JSX 元素这种没法序列化进 manager / URL 的值可以用mapping把简单字符串映射成复杂对象// argTypes 里的 mapping键是 arg 的“值”不是 options 的索引 export default { argTypes: { status: { mapping: { // active - 真实的复杂对象/元素 active: { label: Active, icon: span●/span }, }, }, }, };两个易错点mapping不要求穷尽当前值若不是它的键就直接用原值键永远对应 arg 的值而非options里的下标。完整示例见 docs/_snippets/arg-types-mapping.md。4. 用useArgs同步内部状态。当组件内部状态要反向驱动 args比如开关被点开后Controls 里的勾选态跟着变在故事render里用storybook/preview-api导出的useArgs就能让组件状态和面板双向对齐。三个最容易踩的坑React hooks 与 Storybook hooks 混用会炸。在故事render里一旦用了 Storybook 的 hooks API就别再掺 React 的useState/useEffect/useRef——React hooks 的副作用与重渲染不经过 Storybook 的 hook 上下文二次渲染时直接报错。状态管理统一改用storybook/preview-api里的同名 hooks。Svelte CSF 没法用args传插槽内容。走storybook/addon-svelte-csf时子内容要写在Story开闭标签之间、作为childrensnippet 传入而一旦把渲染完全交给childrenasChild依赖 args 的能力如 Controls就会失效。要传 args 就用标准 CSF 3 写法。Web Components 的类型会退化。因为component传的是元素名字符串推不出类型TS 里Story StoryObj只剩宽泛约束args的校验得交给组件自身的 attribute / property 定义——这是正常的别去硬补泛型。概念到文档路径延伸阅读速查想弄懂去哪看故事是什么、带 args 的 Button 标准示例docs/get-started/whats-a-story.mdxargs 全语义作用域、组合、URL、mapping、useArgsdocs/writing-stories/args.mdxstories 文件存放位置、默认/具名导出规范docs/writing-stories/index.mdxButton 带 args 的标准故事片段原文docs/_snippets/button-story-with-args.mdcomponent args 示例docs/_snippets/button-story-component-args-primary.mdglobal argspreview示例docs/_snippets/args-in-preview.md三层合并的底层实现code/core/src/preview-api/modules/store/csf/prepareStory.ts把整件事压成三句话写一个 Storybook 故事本质就是给一个渲染目标meta指向的组件配上一组输入argsargs按 global → component → story 的顺序合并越靠近故事优先级越高而 Controls、URL 参数、Actions 这些能力全都只是args 会变这件事的自然副产品。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表