文档
Storybook 文档

用 TypeScript 编写故事

TypeScript 中编写故事可以提高你的生产力。你不需要在文件之间跳来跳去查找组件属性。你的代码编辑器会提醒你缺少必需属性,甚至会自动完成属性值,就像在应用程序中使用组件时一样。此外,Storybook 会推断这些组件类型以自动生成 Controls 表格。

Storybook 内置了 TypeScript 支持,因此你可以直接开始使用,无需任何配置。

使用 MetaStoryObj 对故事进行类型化

编写故事时,有两个方面需要进行类型化,这对你有帮助。第一个是 组件元数据,它描述和配置组件及其故事。在 CSF 文件 中,这是默认导出。第二个是 故事本身

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,
  },
};

属性类型参数

MetaStoryObj 类型都是 泛型,因此你可以为它们提供一个可选的属性类型参数,用于组件类型或组件的属性类型(例如,typeof Button 部分的 Meta<typeof Button>)。通过这样做,TypeScript 会阻止你定义无效的参数,并且所有 装饰器播放函数加载器 会对它们的函数参数进行类型化。

上面的示例传递了一个组件类型。请参阅 对自定义参数进行类型化,了解传递属性类型的示例。

使用 satisfies 来提高类型安全性

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

使用 satisfies 来应用故事的类型有助于在跨故事共享 播放函数 时保持类型安全性。如果没有它,TypeScript 会抛出错误,指出 play 函数可能未定义。satisfies 运算符使 TypeScript 能够推断出播放函数是否定义。

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

对自定义参数进行类型化

有时,故事需要定义组件道具中未包含的参数。在这种情况下,您可以使用交集类型来组合组件的道具类型和您的自定义参数类型。例如,以下是如何使用 footer 参数来填充子组件

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',
  },
};