组件 Story 格式 (CSF)
CSF Next 是 Storybook 组件故事格式 (CSF) 的下一代版本。这个新的 API 使用一种称为工厂函数(factory functions)的模式,为您的 Storybook 故事提供完整的类型安全,从而更轻松地正确配置插件并释放 Storybook 功能的全部潜力。
本参考指南提供了 API 的概述以及从先前 CSF 版本升级的迁移指南。
概览
CSF Next API 由函数组成,用于帮助您编写故事。请注意,其中三个函数作为工厂函数运行,每个函数生成链中的下一个函数(definePreview → preview.meta → meta.story),在每个步骤提供完整的类型安全。
defineMain
使用 CSF Next,您的 主 Storybook 配置 由 defineMain 函数指定。此函数是类型安全的,并将自动推断您项目的类型。
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { defineMain } from '@storybook/your-framework/node';
export default defineMain({
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: ['@storybook/addon-a11y'],
});definePreview
同样,definePreview 函数指定您项目的故事配置。此函数也是类型安全的,并将推断您整个项目的类型。
重要的是,通过在此处指定插件,它们的类型将在您的项目中可用,从而实现自动完成和类型检查。
您将在故事文件中导入此函数的结果 preview,以定义组件元数据。
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
import addonA11y from '@storybook/addon-a11y';
export default definePreview({
// 👇 Add your addons here
addons: [addonA11y()],
parameters: {
// type-safe!
a11y: {
options: { xpath: true },
},
},
});在安装插件(通过 npx storybook add <addon-name>)或运行 storybook dev 时,预览配置将自动更新以引用 必需的插件。
preview.meta
preview 对象上的 meta 函数用于定义 故事的元数据。它接受一个包含 component、title、parameters 和其他故事属性的对象。
import preview from '../.storybook/preview';
import { Button } from './Button';
const meta = preview.meta({
component: Button,
parameters: {
// type-safe!
layout: 'centered',
}
});如果您想在预览配置中使用绝对导入而不是相对导入(如下所示),则可以使用子路径导入或别名进行配置。
// ✅ Absolute imports won't break if you move story files around
import preview from '#.storybook/preview';
// ❌ Relative imports can break if you move story files around
import preview from '../../../.storybook/preview';配置
子路径导入是 Node.js 的标准,允许您在项目中定义自定义导入路径,然后可以在整个代码库中使用它们。
要配置子路径导入,请在您的 package.json 中添加以下内容:
{
"imports": {
"#*": ["./*", "./*.ts", "./*.tsx"],
},
}有关更多信息,请参阅 子路径导入文档。
或者,您可以在 构建器(Vite 或 Webpack)中配置别名。
meta.story
最后,meta 对象上的 story 函数定义了故事。此函数接受一个包含 name、args、parameters 和其他故事属性的对象。
// ...from above
const meta = preview.meta({ /* ... */ });
export const Primary = meta.story({
args: {
primary: true,
},
});<Story>.extend
您可以使用 .extend 方法基于现有故事创建新故事,可以选择覆盖或添加新属性。
属性合并详情
属性会智能合并
args进行浅合并parameters进行深合并,数组除外,数组会被替换decorators和tags会被拼接
// ...from above
const meta = preview.meta({ /* ... */ });
export const Example = meta.story({
args: {
primary: true,
exampleArray: ['a', 'b'],
exampleObject: { a: 'a', b: 'b' },
},
parameters: {
exampleArray: ['a', 'b'],
exampleObject: { a: 'a', b: 'b' },
},
tags: ['a'],
});
/*
* 👇 Final values applied:
* {
* args: {
* primary: true,
* disabled: true,
* exampleArray: ['c'],
* exampleObject: { a: 'c' }
* },
* parameters: {
* exampleArray: ['c'],
* exampleObject: { a: 'c', b: 'b' }
* },
* tags: ['a', 'b']
* }
*/
export const ExtendedExample = Example.extend({
args: {
disabled: true,
exampleArray: ['c'],
exampleObject: { a: 'c' },
},
parameters: {
disabled: true,
exampleArray: ['c'],
exampleObject: { a: 'c' },
},
tags: ['b'],
});// ...from above
const meta = preview.meta({ /* ... */ });
export const Primary = meta.story({
args: {
primary: true,
},
});
export const PrimaryDisabled = Primary.extend({
args: {
disabled: true,
},
});<Story>.test
(⚠️ 实验性)
定义 故事测试 的更符合人体工程学的方式。虽然此 API 仍处于实验阶段,但它 在 RFC 中进行了记录以收集反馈,并且必须通过 experimentalTestSyntax 功能标志 来启用。
// ...from above
export const PrimaryDisabled = Primary.extend({ args: { disabled: true } });
// 👇 .test method: Attach tests to a story
// The test function can run the same code as the play function
PrimaryDisabled.test('should be disabled', async ({ canvas, userEvent, args }) => {
const button = await canvas.findByRole('button');
await userEvent.click(button);
await expect(button).toBeDisabled();
await expect(args.onClick).not.toHaveBeenCalled();
});升级到 CSF Next
您可以自动(从 CSF 3)或手动(从 CSF 1、2 或 3)将故事升级到 CSF Next。CSF Next 的设计理念是允许增量使用;您不必一次性升级所有故事文件。但是,您不能在同一个文件中混合使用不同的故事格式。
在继续之前,请确保您使用的是最新版本的 Storybook。您可以使用此命令自动升级您的 Storybook:
npx storybook@latest upgrade自动
您可以使用此命令自动将项目的所有故事从 CSF 3 升级到 CSF Next:
npx storybook automigrate csf-factories它将对您所有故事文件执行下面的所有手动升级步骤。
从 CSF 2 升级到 CSF 3
您必须使用 CSF 3 才能自动升级到 CSF Next。如果您使用的是 CSF 2,则可以首先使用此命令升级到 CSF 3:
npx storybook migrate csf-2-to-3 --glob="**/*.stories.tsx" --parser=tsx手动
您也可以手动将项目的故事文件升级到 CSF Next。在使用 CSF Next 的故事文件之前,您必须升级您的 .storybook/main.js|ts 和 .storybook/preview.js|ts 文件。
1. 更新您的主 Storybook 配置 f文件
将您的 .storybook/main.js|ts 文件更新为使用新的 defineMain 函数。
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
+ import { defineMain } from '@storybook/your-framework/node';
- import { StorybookConfig } from '@storybook/your-framework';
+ export default defineMain({
- export const config: StorybookConfig = {
// ...config itself is unchanged
+ });
- };
- export default config;2. 更新您的预览配置 f文件
将您的 .storybook/preview.js|ts 文件更新为使用新的 definePreview 函数。
哪些插件应在 preview 中指定?
插件提供注解类型(parameters、globals 等)的功能是新的,并非所有插件都支持。
如果插件提供注解(即,它分发了 ./preview 导出),则可以通过两种方式导入:
-
对于官方 Storybook 插件,导入默认导出:
import addonName from '@storybook/addon-name' -
对于社区插件,您应该导入整个模块,并从中访问插件:
import * as addonName from 'community-addon-name'
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
+ import { definePreview } from '@storybook/your-framework';
- import type { Preview } from '@storybook/your-framework';
// 👇 Import the addons you are using
+ import addonA11y from '@storybook/addon-a11y';
+ export default definePreview({
- export const preview: Preview = {
// ...current config
// 👇 Add your addons here
+ addons: [addonA11y()],
+ });
- };
- export default preview;3. 更新您的故事文件
故事文件已更新以提高可用性。使用新格式:
- 从 Storybook 预览文件中导入预览构造
- 元数据对象现在通过
preview.meta函数创建,并且不必导出为默认导出 - 故事现在从元数据对象创建,通过
meta.story函数
下面的示例显示了升级故事文件从 CSF 3 到 CSF Next 所需的更改。您也可以使用类似的步骤从 CSF 1 或 2 升级。
+ import preview from '../.storybook/preview';
- import type { Meta, StoryObj } from '@storybook/your-framework';
import { Button } from './Button';
+ const meta = preview.meta({
- const meta = {
// ...meta object is unchanged
+ });
- } satisfies Meta<typeof Button>;
- export default meta;
- type Story = StoryObj<typeof meta>;
+ export const Primary = meta.story({
- export const Primary: Story = {
// ...story object is unchanged
+ });
- };请注意,导入或手动应用任何类型到元数据或故事已不再是必需的。由于工厂函数模式,类型现在会自动推断。
3.1 重用故事属性
如果您正在重用故事属性以基于现有故事创建新故事,则 <Story>.extend 方法是推荐的实现方式。
以前,在故事属性(如 Story.args 或 Story.parameters)被重用于另一个故事时,会直接访问它们。虽然这样访问仍然受支持,但在 CSF Next 中已被弃用。
所有故事属性现在都包含在一个名为 composed 的新属性中,应该从该属性访问。例如,Story.composed.args 或 Story.composed.parameters。
// ...rest of file
+ export const Primary = meta.story({
- export const Primary: Story = {
args: { primary: true },
+ });
- };
+ export const PrimaryDisabled = meta.story({
- export const PrimaryDisabled: Story = {
args: {
+ ...Primary.composed.args,
- ...Primary.args,
disabled: true,
}
+ });
- };选择“composed”这个属性名是因为其中的值是从故事、其组件元数据和预览配置组合而成的。
如果您想访问故事的直接输入,可以使用 Story.input 而不是 Story.composed。
4. 更新您的 Vitest 设置文件
如果您使用 Storybook 的 Vitest 插件,则可以移除 Vitest 设置文件(.storybook/vitest.setup.ts)。
如果您在 Vitest 中使用 可移植故事,则可以使用 Vitest 设置文件来配置您的故事。此文件必须更新为使用新的 CSF Next 格式。
请注意,这仅在您将 CSF Next 用于所有经过测试的故事时适用。如果您混合使用 CSF 1、2 或 3 和 CSF Next,则必须维护两个单独的设置文件。
import { beforeAll } from 'vitest';
// 👇 No longer necessary
- // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import { setProjectAnnotations } from '@storybook/your-framework';
- import * as addonAnnotations from 'my-addon/preview';
+ import preview from './.storybook/preview';
- import * as previewAnnotations from './.storybook/preview';
// No longer necessary
- const annotations = setProjectAnnotations([previewAnnotations, addonAnnotations]);
// Run Storybook's beforeAll hook
+ beforeAll(preview.composed.beforeAll);
- beforeAll(annotations.beforeAll);5. 在测试文件中重用故事
Storybook 的 Vitest 插件 允许您直接在 Storybook 中测试您的组件。所有故事都将自动转换为 Vitest 测试,从而在您的测试套件中实现无缝集成。
如果您无法使用 Storybook Test,仍然可以使用 可移植故事 在测试文件中重用故事。在以前的故事格式中,您必须在将故事渲染到测试文件中之前组合它们。使用 CSF Next,您现在可以直接重用故事。
import { test, expect } from 'vitest';
import { screen } from '@testing-library/react';
- import { composeStories } from '@storybook/your-framework';
// Import all stories from the stories file
import * as stories from './Button.stories';
+ const { Primary } = stories;
- const { Primary } = composeStories(stories);
test('renders primary button with default args', async () => {
// The run function will mount the component and run all of Storybook's lifecycle hooks
await Primary.run();
const buttonElement = screen.getByText('Text coming from args in stories file!');
expect(buttonElement).not.toBeNull();
});Story 对象还提供了一个 Component 属性,使您能够使用任何您选择的方法(例如 Testing Library)来渲染组件。您还可以通过 composed 属性访问其组合的属性(args、parameters 等)。
以下是如何通过渲染其组件在测试文件中重用故事的示例:
import { test, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
// Import all stories from the stories file
import * as stories from './Button.stories';
const { Primary, Secondary } = stories;
test('renders primary button with default args', async () => {
// Access the story's component via the .Component property
render(<Primary.Component />);
const buttonElement = screen.getByText('Text coming from args in stories file!');
expect(buttonElement).not.toBeNull();
});
test('renders primary button with overridden props', async () => {
// You can override props by passing them directly to the story's component
render(<Primary.Component>Hello world</Primary.Component>);
const buttonElement = screen.getByText(/Hello world/i);
expect(buttonElement).not.toBeNull();
});常见问题解答 (FAQ)
我是否必须将我所有的故事迁移到这个新格式?
Storybook 将在可预见的未来继续支持 CSF 1、CSF 2 和 CSF 3。这些旧格式均未被弃用。
在使用 CSF Next 时,您仍然可以使用旧格式,只要它们不混用在同一个文件中。如果您想将现有文件迁移到新格式,请参阅 上面的升级部分。
这个格式是否适用于 MDX 文档页面?
是的,用于在 MDX 文件中引用故事的 文档块 支持 CSF Next 格式,无需任何更改。
我如何了解更多关于此格式的信息并提供反馈?
有关此实验性格式的原始提案的更多信息,请参阅其 GitHub 上的 RFC。我们欢迎您的评论!
