Docs
Storybook Docs

使用 TypeScript 编写故事

使用 TypeScript 编写故事可以提高你的工作效率。你不必在文件之间跳转来查找组件属性。你的代码编辑器将提醒你缺少必需的 props,甚至自动补齐 prop 值,就像在你的应用中使用组件时一样。此外,Storybook 推断这些组件类型以自动生成 控件 表。

¥Writing your stories in TypeScript makes you more productive. You don't have to jump between files to look up component props. Your code editor will alert you about missing required props and even autocomplete prop values, just like when using your components within your app. Plus, Storybook infers those component types to auto-generate the Controls table.

Storybook 具有内置的 TypeScript 支持,因此你无需任何配置即可开始使用。

¥Storybook has built-in TypeScript support, so you can get started with zero configuration required.

使用 MetaStoryObj 输​​入故事

¥Typing stories with Meta and StoryObj

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

¥When writing stories, there are two aspects that are helpful to type. The first is the component meta, which describes and configures the component and its stories. In a CSF file, this is the default export. The second is the stories themselves.

Storybook 为其中每个都提供了工具类型,名为 MetaStoryObj。这是一个使用这些类型的示例 CSF 文件:

¥Storybook provides utility types for each of these, named Meta and StoryObj. Here's an example CSF file using those types:

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 类型参数

¥Props type parameter

MetaStoryObj 类型都是 generics,因此你可以为它们提供组件类型或组件的 props 类型的可选 prop 类型参数(例如,Meta<typeof Button>typeof Button 部分)。通过这样做,TypeScript 将阻止你定义无效的参数,并且所有 decorators播放函数loaders 都将键入它们的函数参数。

¥Meta and StoryObj types are both generics, so you can provide them with an optional prop type parameter for the component type or the component's props type (e.g., the typeof Button portion of Meta<typeof Button>). By doing so, TypeScript will prevent you from defining an invalid arg, and all decorators, play functions, or loaders will type their function arguments.

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

¥The example above passes a component type. See Typing custom args for an example of passing a props type.

使用 satisfies 实现更好的类型安全

¥Using satisfies for better type safety

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

¥If you are using TypeScript 4.9+, you can take advantage of the new satisfies operator to get stricter type checking. Now you will receive type errors for missing required args, not just invalid ones.

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

¥Using satisfies to apply a story's type helps maintain type safety when sharing a play function across stories. Without it, TypeScript will throw an error that the play function may be undefined. The satisfies operator enables TypeScript to infer whether the play function is defined or not.

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

¥Finally, use of satisfies allows you to pass typeof meta to the StoryObj generic. This informs TypeScript of the connection between the meta and StoryObj types, which allows it to infer the args type from the meta type. In other words, TypeScript will understand that args can be defined both at the story and meta level and won't throw an error when a required arg is defined at the meta level, but not at the story level.

输入自定义参数

¥Typing custom args

有时故事需要定义未包含在组件属性中的参数。对于这种情况,你可以使用 交叉类型 来组合组件的 props 类型和自定义参数的类型。例如,以下是你可以使用 footer 参数填充子组件的方法:

¥Sometimes stories need to define args that aren’t included in the component's props. For this case, you can use an intersection type to combine a component's props type and your custom args' type. For example, here's how you could use a footer arg to populate a child component:

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