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

资讯详情

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

Storybook Args 实战:一个普通对象驱动全框架组件渲染与 URL 覆写

Storybook Args 实战:一个普通对象驱动全框架组件渲染与 URL 覆写 Storybook Args 实战一个普通对象驱动全框架组件渲染与 URL 覆写让 Button 同时呈现 primary 和 secondary 两种状态时复制粘贴两段 JSX 是最笨的解法文案一改就得改两处。Storybook Args 就是为这个痛点准备的——给一个组件故事挂上一个普通 JS 对象组件怎么渲染由这个对象说了算组件源码一行不用动。Args 在渲染链路中扮演什么角色渲染参数契约与三层合并优先级Args 本质上是一份渲染参数契约用键值对描述组件该以什么参数渲染Storybook 负责在渲染前把这些值合并、增强再交给渲染器消费。它像函数签名——契约只声明要什么输入框架保证输入准时到。契约可以写在三个位置各自的作用范围不同层级挂载位置作用范围被谁覆盖Story args故事对象上的args字段仅该故事自身优先级最高Component argsmeta默认导出里的args键该组件的全部故事被故事级 args 覆盖Global args.storybook/preview默认导出里的args所有组件的所有故事被上面两层依次覆盖三层的胜负关系在源码里有直接证据prepareStory.ts 中按全局 → 组件 → 故事的顺序展开const passedArgs: Args { ...projectAnnotations.args, ...componentAnnotations.args, ...storyAnnotations?.args, } as Args;后写的来源覆盖先写的来源所以故事级最优先、全局级垫底这一步发生在故事准备阶段与组件自己的 props 声明完全解耦。合并后的参数还会经过参数增强器流水线加工比如从 argTypes 推导默认值。上图里primary、label、backgroundColor等参数都能实时修改它们背后就是这份契约。既然优先级清楚了第一个要落地的问题就是args 到底写在故事的哪个字段里。CSF 3 下写第一个带 Args 的组件故事要不要手写 renderCSF 3 用Meta/StoryObj类型给出强类型推导是当前推荐的主流写法。按是否需要手写 render 函数可以把各框架分成两组写法差异一目了然。无需 render 的框架React / Angular / Solid / Svelte / Web Components这类渲染器会自动把 args 映射成组件输入故事对象里只需要挂args。React 作为 TS 代表给出类型桥接写法import type { Meta, StoryObj } from storybook/react; 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 }, };Angular 走另一条类型路径类型参数直接传组件类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 }, };Solid、Svelte标准 CSF与 Web Components 都只给 JS 最简形态import { Button } from ./Button; export default { component: Button }; export const Primary { args: { label: Button, primary: true }, };export default { component: demo-button }; export const Primary { args: { primary: true, label: Button }, };这组的共同点是渲染器替你完成 args 到 props /Input的映射。类型路径要分开记React 用satisfies Metatypeof Button加StoryObjtypeof meta从组件反推Angular 用MetaButton直接传类。Web Components 的component只能是自定义元素名字符串没有模块可供反推args 的类型约束就退给了元素自身的 attribute 定义。需要手写 render 的框架Vue 3 / HTML / Preact / Svelte CSF这一组的共同要求是render 函数内部必须真正消费args。Vue 3 把 args 以v-bind整体透传给组件import Button from ./Button.vue; export default { component: Button }; export const Primary { render: (args) ({ components: { Button }, setup() { return { args }; }, template: Button v-bindargs /, }), 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; const mode args.primary ? storybook-button--primary : storybook-button--secondary; btn.className [storybook-button, storybook-button--medium, mode].join( ); return btn; }, args: { primary: true, label: Button }, };Preact 用 JSX 展开最省事Svelte CSF 则把 args 交给Story组件/** jsx h */ import { h } from preact; import { Button } from ./Button; export default { component: Button }; export const Primary { render: (args) Button {...args} /, args: { primary: true, label: Button }, };script module import { defineMeta } from storybook/addon-svelte-csf; import Button from ./Button.svelte; const { Story } defineMeta({ component: Button }); /script Story namePrimary args{{ primary: true, label: Button }} /这组的要点全在 render 内部Vue 靠v-bind透传HTML 靠手工拼节点Preact 靠 JSX 展开Svelte CSF 的 args 装不下插槽内容children 要写在Story开闭标签之间。只要 render 里消费了 argsControls、URL 覆写这些能力照单全收。语法落定之后CSF 3 里默认导出 具名导出的隐式约定其实可以变得更直白。CSF Next 链式写法preview.meta 与 meta.storyCSF 3 的 meta 藏在默认导出这条约定里类型要从typeof meta二次反推。CSF Next 把两者都显式化先从.storybook/preview拿preview.meta()创建 meta 对象再调meta.story()派生每个故事类型信息顺着调用链一路传递。选两个代表框架各贴一份完整示例。React 属于无需 render 的形态import preview from ../.storybook/preview; import { Button } from ./Button; const meta preview.meta({ component: Button }); export const Primary meta.story({ args: { primary: true, label: Button }, });Vue 3 属于需要 render 的形态渲染逻辑整体搬进meta.story()import preview from ../.storybook/preview; import Button from ./Button.vue; const meta preview.meta({ component: Button }); export const Primary meta.story({ render: (args) ({ components: { Button }, setup() { return { args }; }, template: Button v-bindargs /, }), args: { primary: true, label: Button }, });两套写法的关键差异可以收成三行维度CSF 3CSF Nextmeta 来源默认导出的 meta 对象自己组装preview.meta()创建继承 preview 的全局配置故事导出语法具名导出的普通对象meta.story({...})的链式结果类型推导入口satisfies Metatypeof XStoryObjtypeof metapreview.meta()从组件直接反推 meta 的具体类型仓库示例里 CSF Next 仍带 实验标记采用前需要 preview 模块按新语法组织。语法之外更实用的是复用与传参同一个组件的一堆故事、一条分享链接、一个没法序列化的 JSX各有各的招。Args 复用、URL 参数覆写与 mapping 映射规则args 本身是普通对象ES2015 的对象展开就能复用export const PrimaryLongName: Story { args: { ...Primary.args, label: Button 长文本场景 }, };多数故事共享的 args 更推荐上提到 component args故事里只写差异。除了面板操作args 还能直接写进 URL典型链接形如?path/story/avatar--defaultargsstyle:rounded;size:100args恒为key: value集合以分号;分隔值会被强转成 argTypes 类型对象与数组都支持null与undefined加!null、!undefined前缀例如nil:!null日期编码为!date()ISO 字符串颜色编码为!hex()、!rgba()或!hsla()出于 XSS 防护URL 中的键值只允许字母数字、空格、下划线与连字符其余类型会被忽略并从 URL 移除像 JSX 元素这类进不了 URL 和 Controls 的复杂值用argTypes.mapping把简单字符串映射回复杂值且 mapping 的键对应的是 arg 的值而非options索引不必穷尽argTypes: { label: { control: { type: select }, options: [Normal, Bold, Italic], mapping: { Bold: bBold/b, Italic: iItalic/i }, }, },复用与链接传参解决的是输入侧更有意思的是输出侧为什么动一下 Controls 滑块预览区就立刻重渲染了Controls 面板为什么能实时重渲染触发链路很短args 一变Storybook 重新走一遍故事准备流程——prepareStory把故事 全部装饰器 参数打包成一个无状态的渲染函数用新 args 重新求值组件随之 re-render所有依赖 args 的 addon 沿同一条链路同步刷新。交互回调比如 onClick会被 Actions 面板自动记录点击记录即可看到那次事件传入的参数。如果组件内部状态需要反向驱动 args例如开关被点击后同步 Controls 的选中态在渲染函数里用storybook/preview-api导出的useArgs读写即可。⚠️ 官方明确警告渲染函数中不要混用 React 的useState/useEffect/useRef与 Storybook 的 hooks——React hooks 的副作用不经过 Storybook 的 hook 上下文会在二次渲染时报错状态管理应统一改用 preview-api 的等价 hooks。想继续深挖的话文档与源码在仓库里已有一条清晰的阅读路径。仓库内延伸路径docs/get-started/whats-a-story.mdx初次认识故事是什么时读带运行截图docs/writing-stories/args.mdx故事跑通后读专攻三层作用域与组合code/core/src/preview-api/modules/store/csf/prepareStory.ts可继续追 argsEnhancers 流水线如何加工 initialArgs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表