如何确保TypeScript联合类型在代码中始终保持窄化
问题:如何让TypeScript保持
item.args的窄化类型? 我希望通过创建存储所需信息的对象替代大量if语句,让执行流程更线性。以下是示例代码:
type ItemOne = { type: 'itemOne' fn: typeof funcOne args: Parameters<typeof funcOne>[0] } type ItemTwo = { type: 'itemTwo', fn: typeof funcTwo args: Parameters<typeof funcTwo>[0] } type Item = ItemOne | ItemTwo function funcOne({ name }: { name: string }) { console.log(name) } function funcTwo({ age }: { age: number }) { console.log(age) } function createItem(shouldCreateItemOne: boolean): Item { if (shouldCreateItemOne) { return { type: 'itemOne', fn: funcOne, args: { name: 'Dave' } } as const } return { type: 'itemTwo', fn: funcTwo, args: { age: 22 } } as const } const item = createItem(false) item.fn(item.args)
调用item.fn(item.args)时,TypeScript报错:Argument of type '{ name: string; } | { age: number; }' is not assignable to parameter of type '{ name: string; } & { age: number; }'.
解决方案
方法1:让createItem返回具体类型(泛型约束)
通过给createItem添加泛型,让TypeScript根据输入参数推断返回的具体Item子类型,而非宽泛的联合类型:
function createItem<T extends boolean>(shouldCreateItemOne: T): T extends true ? ItemOne : ItemTwo { if (shouldCreateItemOne) { return { type: 'itemOne', fn: funcOne, args: { name: 'Dave' } } as const as any; } return { type: 'itemTwo', fn: funcTwo, args: { age: 22 } } as const as any; } const item = createItem(false); // 类型被推断为ItemTwo item.fn(item.args); // 无报错
方法2:用类型守卫/switch语句手动窄化类型
通过type字段做分支判断,让TypeScript在每个分支中明确当前item的具体类型,从而正确关联fn和args:
const item = createItem(false); switch(item.type) { case 'itemOne': item.fn(item.args); // args被窄化为{ name: string } break; case 'itemTwo': item.fn(item.args); // args被窄化为{ age: number } break; }
方法3:重构类型定义,强关联type、fn和args
用映射类型定义通用的FuncMap,让Item类型自动生成强关联的联合类型,从根源避免类型不匹配:
// 先定义所有函数的映射 type FuncMap = { itemOne: typeof funcOne, itemTwo: typeof funcTwo }; // 自动生成关联的Item类型 type Item<T extends keyof FuncMap = keyof FuncMap> = { [K in T]: { type: K, fn: FuncMap[K], args: Parameters<FuncMap[K]>[0] } }[T]; // createItem的返回类型会自动推断为正确的子类型 function createItem(shouldCreateItemOne: boolean): Item { if (shouldCreateItemOne) { return { type: 'itemOne', fn: funcOne, args: { name: 'Dave' } } as const; } return { type: 'itemTwo', fn: funcTwo, args: { age: 22 } } as const; } // 配合条件判断即可正确窄化 const item = createItem(false); if (item.type === 'itemTwo') { item.fn(item.args); }
报错原因
TypeScript无法自动追踪联合类型中fn和args的对应关系。当直接调用item.fn(item.args)时,它会把fn的参数类型合并为所有可能参数的交集(同时满足funcOne和funcTwo的参数要求),而args是所有可能参数的并集,两者自然无法匹配。通过上述方法,我们让TypeScript明确当前fn对应的具体args类型,从而解决报错。
内容的提问来源于stack exchange,提问作者Kristian Roebuck
相关产品推荐
相关产品推荐

