用 TypeScript 编写 Stories
在 TypeScript 中编写故事可以提高您的工作效率。您无需在文件之间跳转来查找组件 props。您的代码编辑器会提示您缺少必需的 props,甚至可以像在应用中使用组件一样自动补全 props 值。此外,Storybook 会推断出这些组件类型,从而自动生成 Controls 表。
Storybook 内置了 TypeScript 支持,因此您可以零配置开始使用。
使用 Meta 和 StoryObj 为故事添加类型
在编写故事时,有两个方面对添加类型很有帮助。首先是 组件元数据 (component meta),它描述和配置组件及其故事。在 CSF 文件 中,这就是默认导出的内容。其次是 故事本身。
Storybook 为这两者提供了实用工具类型,分别命名为 Meta 和 StoryObj。以下是一个使用这些类型的 CSF 文件示例:
// 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 类型参数
Meta 和 StoryObj 类型都是 泛型,因此您可以为它们提供一个可选的 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 了解 meta 和 StoryObj 类型之间的联系,从而能够从 meta 类型推断出 args 类型。换句话说,TypeScript 将理解 args 可以在故事和元数据级别定义,并且当必需的 arg 在元数据级别定义但在故事级别未定义时,它不会抛出错误。
为自定义 args 添加类型
有时故事需要定义不在组件 props 中的 args。在这种情况下,您可以使用 交叉类型 来组合组件的 props 类型和自定义 args 的类型。例如,以下是如何使用 footer arg 来填充子组件:
// 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;