Is there a way to create a generic function that returns a specific type, if the generic is constrained to a discriminated union?

huangapple go评论57阅读模式
英文:

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 时,编译器无法确定你尝试构建的是哪种 FruitApple 还是 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[&quot;kind&quot;] (i.e. Fruit[&quot;kind&quot;]) evaluates to &#39;apple&#39; | &#39;orange&#39;. 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&lt;T extends Fruit&gt;(fruitToCheckOrCreate: T): T {
const filteredFruits =  myFruits.filter((fruit): fruit is T =&gt; fruit.kind === fruitToCheckOrCreate.kind);   
const fruitInMyFruits = filteredFruits.find((fruit) =&gt;fruit.name === fruitToCheckOrCreate.name);
const newFruit = fruitToCheckOrCreate
return fruitInMyFruits || newFruit;
}

https://tsplay.dev/Ndaydm

huangapple
  • 本文由 发表于 2023年7月10日 16:37:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76652065.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定