You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在判别式联合约束下实现返回特定类型的泛型获取或创建函数?

泛型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

优化点说明

  1. 映射类型关联kind与类型:通过FruitKindMap明确每个kind值对应的具体水果类型,消除了泛型的模糊性。
  2. 自动推断泛型:函数的泛型K由传入的fruitKind参数自动推断,调用时无需手动指定,更简洁且不易出错。
  3. 类型安全保障:TypeScript能确认newFruit的结构完全符合FruitKindMap[K]的要求,解决了原代码的类型不匹配问题。

内容的提问来源于stack exchange,提问作者smfrdev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 23:04:53