文档
Storybook 文档

用 TypeScript 编写 Stories

TypeScript 中编写故事可以提高您的工作效率。您无需在文件之间跳转来查找组件 props。您的代码编辑器会提示您缺少必需的 props,甚至可以像在应用中使用组件一样自动补全 props 值。此外,Storybook 会推断出这些组件类型,从而自动生成 Controls 表。

Storybook 内置了 TypeScript 支持,因此您可以零配置开始使用。

使用 MetaStoryObj 为故事添加类型

在编写故事时,有两个方面对添加类型很有帮助。首先是 组件元数据 (component meta),它描述和配置组件及其故事。在 CSF 文件 中,这就是默认导出的内容。其次是 故事本身

Storybook 为这两者提供了实用工具类型,分别命名为 MetaStoryObj。以下是一个使用这些类型的 CSF 文件示例:

Button.stories.ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta = {
  component: Button,
} satisfies Meta<typeof Button>;
export default meta;
 
type Story = StoryObj<typeof meta>;
 
export const Basic = {} satisfies Story;
 
export const Primary = {
  args: {
    primary: true,
  },
} satisfies Story;

Props 类型参数

MetaStoryObj 类型都是 泛型,因此您可以为它们提供一个可选的 props 类型参数,用于组件类型或组件的 props 类型(例如,Meta<typeof Button> 中的 typeof Button 部分)。通过这样做,TypeScript 会阻止您定义无效的 arg,并且所有 装饰器play 函数loader 都将对其函数参数进行类型检查。

上面的示例传递了一个组件类型。请参阅 为自定义 args 添加类型 以获取传递 props 类型类型的示例。

使用 satisfies 以获得更好的类型安全性

如果您使用的是 TypeScript 4.9+,您可以利用新的 satisfies 操作符来获得更严格的类型检查。现在您将收到关于缺失必需 args 的类型错误,而不仅仅是无效 args。

使用 satisfies 来应用故事的类型,有助于在故事之间共享 play 函数时维护类型安全。如果没有它,TypeScript 会抛出 play 函数可能未定义的错误。satisfies 操作符使 TypeScript 能够推断 play 函数是否已定义。

最后,使用 satisfies 允许您将 typeof meta 传递给 StoryObj 泛型。这使得 TypeScript 了解 metaStoryObj 类型之间的联系,从而能够从 meta 类型推断出 args 类型。换句话说,TypeScript 将理解 args 可以在故事和元数据级别定义,并且当必需的 arg 在元数据级别定义但在故事级别未定义时,它不会抛出错误。

为自定义 args 添加类型

有时故事需要定义不在组件 props 中的 args。在这种情况下,您可以使用 交叉类型 来组合组件的 props 类型和自定义 args 的类型。例如,以下是如何使用 footer arg 来填充子组件:

Page.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Page } from './Page';
 
type PagePropsAndCustomArgs = React.ComponentProps<typeof Page> & { footer?: string };
 
const meta = {
  component: Page,
  render: ({ footer, ...args }) => (
    <Page {...args}>
      <footer>{footer}</footer>
    </Page>
  ),
} satisfies Meta<PagePropsAndCustomArgs>;
export default meta;
 
type Story = StoryObj<typeof meta>;
 
export const CustomFooter = {
  args: {
    footer: 'Built with Storybook',
  },
} satisfies Story;