Storybook 8与Angular 18渲染函数类型错误:无法赋值给ArgsStoryFn
问题场景
为包含@Output EventEmitter的Angular组件编写Storybook故事时,出现类型不兼容错误:故事中定义的args参数类型与Storybook渲染器期望的类型不匹配,提示缺少组件的私有成员(如示例中的store)。移除@Output后问题消失,但业务需要保留事件发射器,临时用any规避类型检查并非最佳实践。
组件示例代码:
class MyComponent { @Output() someEvent = new EventEmitter<any>(); constructor( private readonly store: Store, ) {} // 其他属性和方法 }
原故事代码(触发错误):
export default meta; type Story = StoryObj<MyComponent>; export const Basic: Story = { render: (args: MyComponent) => ({ props: { ...args, }, }), args: {}, };
错误提示:
Type '(args: MyComponent) => { props: { someEvent: EventEmitter<...>; }; }' is not assignable to type 'ArgsStoryFn<AngularRenderer, TransformInputSignalType<TransformOutputSignalType
>>'.
Types of parameters 'args' and 'args' are incompatible.
Type 'TransformInputSignalType<TransformOutputSignalType>' is missing the following properties from type 'MyComponent': store
原因分析
Storybook的Angular渲染器会对组件的输入输出属性做类型转换(比如处理Angular 18的信号输入、事件发射器的类型映射),最终传递给render函数的args只包含可用于模板绑定的公共属性(@Input、@Output),不包含组件的私有成员(如store)。而直接用MyComponent作为StoryObj的泛型参数,会让TypeScript期望args包含组件的所有成员(包括私有属性),导致类型不匹配。
正确解决方案
方案1:使用Storybook的ComponentPropsType提取可绑定属性
利用Storybook提供的ComponentPropsType工具类型,自动提取组件的可绑定公共属性,排除私有成员:
import { Meta, StoryObj, ComponentPropsType } from '@storybook/angular'; import { MyComponent } from './my.component'; const meta: Meta<MyComponent> = { component: MyComponent, }; export default meta; // 使用ComponentPropsType获取组件的可绑定属性类型 type Story = StoryObj<ComponentPropsType<MyComponent>>; export const Basic: Story = { render: (args) => ({ props: args, }), args: {}, };
或者更简洁地直接复用meta的类型:
type Story = StoryObj<typeof meta>;
方案2:手动排除私有成员
如果需要更精细控制类型,可以手动排除组件的私有属性:
import { Meta, StoryObj } from '@storybook/angular'; import { MyComponent } from './my.component'; const meta: Meta<MyComponent> = { component: MyComponent, }; export default meta; // 排除私有成员store type MyComponentProps = Omit<MyComponent, 'store'>; type Story = StoryObj<MyComponentProps>; export const Basic: Story = { render: (args) => ({ props: args, }), args: {}, };
验证效果
以上两种方案都能让args类型与Storybook渲染器期望的类型匹配,消除类型错误,同时保留EventEmitter的类型安全检查,无需使用any。
内容的提问来源于stack exchange,提问作者Ben Thomson

