文档
Storybook Docs

使用 TypeScript 编写 Stories

使用 TypeScript 编写你的 stories 可以提高你的效率。你无需在文件之间跳转来查找组件 props。你的代码编辑器会提醒你缺少必需的 props,甚至可以自动完成 prop 值,就像在你的应用程序中使用组件时一样。此外,Storybook 会推断这些组件类型,以自动生成 Controls 表格。

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

使用 MetaStoryObj 类型定义 stories

在编写 stories 时,有两个方面有助于进行类型定义。第一个是 组件元数据 (component meta),它描述和配置组件及其 stories。在 CSF 文件 中,这是默认导出。第二个是 stories 本身

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

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

Props 类型参数

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

上面的示例传递了一个组件类型。有关传递 props 类型的示例,请参阅 类型化自定义 args

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

如果你正在使用 TypeScript 4.9+,你可以利用新的 satisfies 运算符来获得更严格的类型检查。现在,你将收到缺少必需 args 的类型错误,而不仅仅是无效的 args。

使用 satisfies 来应用 story 的类型有助于在跨 stories 共享 play 函数 时保持类型安全。如果没有它,TypeScript 将抛出一个错误,即 play 函数可能未定义。satisfies 运算符使 TypeScript 能够推断 play 函数是否已定义。

最后,使用 satisfies 允许你将 typeof meta 传递给 StoryObj 泛型。这会将 metaStoryObj 类型之间的连接告知 TypeScript,从而使其能够从 meta 类型推断出 args 类型。换句话说,TypeScript 将理解 args 可以在 story 和 meta 级别定义,并且当必需的 arg 在 meta 级别定义,但不在 story 级别定义时,不会抛出错误。

类型化自定义 args

有时,stories 需要定义组件 props 中未包含的 args。对于这种情况,你可以使用 交叉类型 (intersection type) 来组合组件的 props 类型和你自定义 args 的类型。例如,下面是如何使用 footer arg 来填充子组件的示例

Page.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { Page } from './Page';
 
type PagePropsAndCustomArgs = React.ComponentProps<typeof Page> & { footer?: string };
 
const meta: Meta<PagePropsAndCustomArgs> = {
  component: Page,
  render: ({ footer, ...args }) => (
    <Page {...args}>
      <footer>{footer}</footer>
    </Page>
  ),
};
export default meta;
 
type Story = StoryObj<PagePropsAndCustomArgs>;
 
export const CustomFooter: Story = {
  args: {
    footer: 'Built with Storybook',
  },
};