英文:
Is there a way to create a generic function that returns a specific type, if the generic is constrained to a discriminated union?
问题
I'm trying to create a generic get-or-create function that accepts the tag of the union and wants the output as the specific type of that tag.
但是我得到了错误,newFruit
无法赋值给返回类型:
但是我得到了错误,newFruit
无法赋值给返回类型:
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
类型,所以不太清楚我忽略了什么。有哪些情况下 T
被实例化为与新对象无关的类型?还有更好的方法来实现这个吗?
英文:
I'm trying to create a generic get-or-create function that accepts the tag of the union and want the output as the specific type of that tag.
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})
But I'm getting the error that newFruit
is not assignable to the return type :
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"]; }'.
I've constrained T
to be of type Fruit
so I'm not sure what I'm overlooking. What are the cases for when T
is instantiated as unrelated to the new object? And is there a better way to do this?
答案1
得分: 0
问题出在传递参数给 getOrCreateFruit
函数的方式上。由于参数是逐个传递的,你失去了区分联合类型的能力。
每个属性实际上是 Fruit
所有可能值的联合类型。例如,T["kind"]
(即 Fruit["kind"]
)计算为 'apple' | 'orange'
。因此,当你重建对象 newFruit
时,编译器无法确定你尝试构建的是哪种 Fruit
(Apple
还是 Orange
)。
如果你改变参数,传递一个实际的水果回退对象(其中包含你想要提取的函数检查的属性),TypeScript 编译器将理解你在 newFruit
情况下返回一个有效的 Fruit
。
function getOrCreateFruit<T extends Fruit>(fruitToCheckOrCreate: T): T {
const filteredFruits = myFruits.filter((fruit): fruit is T => fruit.kind === fruitToCheckOrCreate.kind);
const fruitInMyFruits = filteredFruits.find((fruit) => fruit.name === fruitToCheckOrCreate.name);
const newFruit = fruitToCheckOrCreate
return fruitInMyFruits || newFruit;
}
你可以在这里查看示例:链接
英文:
The problem occurs in the way that the arguments are being passed to getOrCreateFruit
. As the arguments are passed in individually, you lose the power of the discriminated union type.
Each property is actually a union of the all the possible values for Fruit
, for example T["kind"]
(i.e. Fruit["kind"]
) evaluates to 'apple' | 'orange'
. So by the time you are rebuilding the object newFruit
, the compiler can't tell what type of Fruit
(Apple
or Orange
) you are attempting to build.
If you instead rework the argument to pass in an actual fruit fallback object (which contains the properties you want to extract for the function checks), the TS compiler will understand that you are returning a valid Fruit
in the newFruit
case.
function getOrCreateFruit<T extends Fruit>(fruitToCheckOrCreate: T): T {
const filteredFruits = myFruits.filter((fruit): fruit is T => fruit.kind === fruitToCheckOrCreate.kind);
const fruitInMyFruits = filteredFruits.find((fruit) =>fruit.name === fruitToCheckOrCreate.name);
const newFruit = fruitToCheckOrCreate
return fruitInMyFruits || newFruit;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论