为什么TypeScript在执行展开操作时会丢失类型推断?
问题重现
先看核心代码场景:
首先是包含必填属性的接口定义:
export interface CheckoutAccordionItemProps { isValid: boolean | null; stepNumber: string; text: string; children: ReactNode; }
接着定义Storybook的meta对象,初始赋值时TypeScript能正常校验必填属性(删除任意一个必填项都会触发报错):
const meta = { args: { items: [ { isValid: null, stepNumber: '1', text: 'Customer Information', children: 'Content', }, ], }, }
但在创建具体Story时,展开meta.args.items[0]并修改属性后,TypeScript抛出类型错误:
export const Valid: StoryObj<typeof meta> = { args: { items: [ { ...meta.args.items[0], isValid: true, }, ], }, };
报错信息:
类型
{ isValid: true; stepNumber?: string | undefined; text?: string | undefined; children?: string | undefined; }无法赋值给类型CheckoutAccordionItemProps。属性stepNumber的类型不兼容,string | undefined无法赋值给string类型,undefined无法赋值给string...
我单独做了一个不依赖Storybook的测试示例,类型推断完全正常:
interface Person { name: string; age: number; } type People = Person[]; const p: Person = { name: "Mark", age: 39, }; const people: People = [p]; const anotherP = { ...people[0], age: 40, };
原因分析
问题根源在Storybook的Meta和StoryObj类型的内部处理逻辑:
- 当让TypeScript自动推断
meta的类型时,Storybook的类型定义会对args做一层包装(比如使用Partial或自定义工具类型),导致meta.args.items[0]被推断为所有属性可选的类型,而非原本的CheckoutAccordionItemProps。 - 虽然初始赋值时TypeScript会校验
meta.args是否符合目标类型,但展开操作只能基于推断出的typeof meta.args.items[0]进行类型判断,自然会认为属性可能为undefined。 - 独立测试示例没有依赖Storybook的类型,TypeScript能直接保留
Person的必填属性类型,因此展开后不会出现类型错误。
解决办法
显式指定
meta的类型(最优解)
不要依赖TypeScript自动推断,直接给meta指定基于CheckoutAccordionItemProps的Meta类型,让meta.args.items[0]的类型明确为必填的CheckoutAccordionItemProps:import type { Meta, StoryObj } from '@storybook/react'; const meta: Meta<{ items: CheckoutAccordionItemProps[] }> = { args: { items: [ { isValid: null, stepNumber: '1', text: 'Customer Information', children: 'Content', }, ], }, };提前赋值并指定变量类型
将meta.args.items[0]赋值给一个明确类型的变量后再展开:const baseItem: CheckoutAccordionItemProps = meta.args.items[0]; export const Valid: StoryObj<typeof meta> = { args: { items: [ { ...baseItem, isValid: true, }, ], }, };类型断言(临时快速解决)
就是你提到的方式,直接对展开的对象做类型断言,虽然不够优雅,但能快速修复问题:export const Valid: StoryObj<typeof meta> = { args: { items: [ { ...(meta.args.items[0] as CheckoutAccordionItemProps), isValid: true, }, ], }, };
内容的提问来源于stack exchange,提问作者CodeFinity

