如何在判别式联合约束下实现返回特定类型的泛型获取或创建函数?
泛型Get-Or-Create函数的TypeScript类型错误解决
问题背景
想要实现一个泛型get-or-create函数,接收联合类型的标签(kind字段),返回该标签对应的特定类型。代码实现如下,但遇到了类型错误:
原代码
type Apple = { name: string kind: "apple" props: { fleshiness: number } } type Orange = { name:string kind: "orange" props: { juiciness: number } } type Fruit = Apple | Orange const myFruits:Fruit[] = [ { name: "fruit2", kind: "orange", props: { juiciness: 0 } }, { name: "fruit1", kind: "apple", props: { fleshiness: 1 } } ] function getOrCreateFruit<T extends Fruit>(fruitName:string, fruitKind:T["kind"], props:T["props"]): T { const filteredFruits = myFruits.filter((fruit): fruit is T => fruit.kind === fruitKind); const fruitInMyFruits = filteredFruits.find((fruit) =>fruit.name === fruitName); const newFruit = { name: fruitName, kind: fruitKind, props: props } return fruitInMyFruits || newFruit; } const orange = getOrCreateFruit<Orange>("fruit1", "orange", {juiciness: 0}) const apple = getOrCreateFruit<Apple>("fruit2", "apple", {fleshiness:1})
错误信息
Type 'T | { name: string; kind: T["kind"]; props: T["props"]; }' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | { name: string; kind: T["kind"]; props: T["props"]; }'.
错误原因分析
虽然T被约束为Fruit,但TypeScript无法保证{name: string; kind: T["kind"]; props: T["props"]}完全等于T。比如存在这样的调用场景:
// 手动指定T为Apple的子类型,带额外属性 getOrCreateFruit<Apple & { extra: boolean }>("test", "apple", { fleshiness: 5 });
此时T包含额外的extra属性,但newFruit并没有这个属性,导致类型不匹配——这就是错误提示中“T可能被实例化为与新对象无关类型”的具体场景。
原代码的泛型设计允许T是Fruit的任意子类型,而不仅仅是联合类型中的成员(Apple或Orange),因此TypeScript无法确认newFruit的结构完全符合T的要求。
优化实现方式
我们可以通过映射类型明确kind与具体水果类型的对应关系,让TypeScript自动推断正确的返回类型,同时避免手动指定泛型的麻烦:
优化后的代码
type Apple = { name: string kind: "apple" props: { fleshiness: number } } type Orange = { name:string kind: "orange" props: { juiciness: number } } // 定义kind到具体水果类型的映射 type FruitKindMap = { apple: Apple; orange: Orange; }; type Fruit = Apple | Orange const myFruits:Fruit[] = [ { name: "fruit2", kind: "orange", props: { juiciness: 0 } }, { name: "fruit1", kind: "apple", props: { fleshiness: 1 } } ] function getOrCreateFruit<K extends keyof FruitKindMap>( fruitName: string, fruitKind: K, props: FruitKindMap[K]["props"] ): FruitKindMap[K] { // 过滤出对应kind的水果,类型断言为映射后的具体类型 const filteredFruits = myFruits.filter( (fruit): fruit is FruitKindMap[K] => fruit.kind === fruitKind ); const existingFruit = filteredFruits.find(fruit => fruit.name === fruitName); // 构造新水果,类型断言为对应类型(结构完全匹配) const newFruit = { name: fruitName, kind: fruitKind, props: props } as FruitKindMap[K]; return existingFruit || newFruit; } // 调用时无需手动指定泛型,TypeScript自动推断类型 const orange = getOrCreateFruit("fruit1", "orange", { juiciness: 0 }); // 类型为Orange const apple = getOrCreateFruit("fruit2", "apple", { fleshiness: 1 }); // 类型为Apple
优化点说明
- 映射类型关联kind与类型:通过
FruitKindMap明确每个kind值对应的具体水果类型,消除了泛型的模糊性。 - 自动推断泛型:函数的泛型
K由传入的fruitKind参数自动推断,调用时无需手动指定,更简洁且不易出错。 - 类型安全保障:TypeScript能确认
newFruit的结构完全符合FruitKindMap[K]的要求,解决了原代码的类型不匹配问题。
内容的提问来源于stack exchange,提问作者smfrdev
相关产品推荐
相关产品推荐

