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

TypeScript转译器导致的意外类型交集问题

TypeScript泛型函数返回区分联合类型的困惑与解决方法

问题场景

先定义一个存储区分联合类型的映射类型:

type M = {
    foo: {a: "foo"};
    bar: {a: "bar"};
};

目标是编写一个函数,根据传入的key值,构造并返回对应类型的实例,比如:

const a1 = f1("foo"); // 期望返回 {a: "foo"}
const b1 = f1("bar"); // 期望返回 {a: "bar"}

尝试1:直接写法(类型检查失败)

直接按泛型约束编写函数时,无法通过TS类型检查:

function f1<T extends keyof M>(a: T): M[T] {
    return {a};
    /*
    错误提示:
    Type '{ a: keyof M; }' is not assignable to type 'M[T]'.
        Type '{ a: keyof M; }' is not assignable to type 'never'.
        The intersection '{ a: "foo"; } & { a: "bar"; }' was reduced
         to 'never' because property 'a' has conflicting types in some
         constituents.(2322)
    */
}

尝试2:返回已有实例(类型检查通过,但不符合需求)

如果预先定义好实例,再通过key返回,TS能正常通过检查,但这种方式无法动态创建新实例,不符合需求:

const v = {
    foo: {a: "foo" as const},
    bar: {a: "bar" as const},
};

type V = typeof v;

function f2<T extends keyof V>(a: T): V[T] {
    return v[a]; // 无类型错误
}

const a2 = f2("foo"); // {a: "foo"}
const b2 = f2("bar"); // {a: "bar"}

注意这里的V和前面的M类型完全一致。

尝试3:返回嵌套对象的属性(类型检查再次失败)

即使通过嵌套对象获取对应值,依然无法通过类型检查:

const c = {
    foo: {x: {a: "foo" as const}},
    bar: {x: {a: "bar" as const}},
};

function f<T extends keyof M>(a: T): M[T] {
    return c[a].x;
    /*
    错误提示:
        Type '{ a: "foo"; } | { a: "bar"; }' is not assignable to type
         'M[T]'.
        Type '{ a: "foo"; }' is not assignable to type 'M[T]'.
            Type '{ a: "foo"; }' is not assignable to type 'never'.
            The intersection '{ a: "foo"; } & { a: "bar"; }' was reduced
             to 'never' because property 'a' has conflicting types in
             some constituents.(2322)
    */
}

核心疑问

为什么TypeScript在尝试1和尝试3中会对所有联合类型取交集,最终得到never类型导致报错,而尝试2却能正常工作?有没有办法让尝试1的写法正常运行?


原因分析

尝试2正常工作的原因

在f2中,v[a]的类型被TypeScript正确推断为分布式索引访问类型。因为v是一个具体的对象,TS可以跟踪到每个key对应的精确类型;当泛型T是联合类型时,V[T]会自动分布到每个联合成员上(即V["foo"] | V["bar"]),而v[a]的运行时值正好对应V[T]的类型,因此TS认为这是安全的。

尝试1和尝试3报错的原因

  • 尝试1中,{a}的类型被推断为{a: keyof M},也就是{a: "foo" | "bar"}。由于泛型T是keyof M的子类型,TS需要验证这个对象是否能赋值给所有可能的M[T]类型(包括T为整个联合类型"foo" | "bar"的情况)。但{a: "foo" | "bar"}无法同时满足{a: "foo"}和{a: "bar"}的交集要求,而这两个类型的交集是never,因此报错。
  • 尝试3中,c[a].x的类型是{a: "foo"} | {a: "bar"},同样,TS无法确定这个联合类型中的哪一个成员对应当前泛型T的M[T]类型,因此会检查该联合类型是否能赋值给所有M[T]的交集(即never),最终导致报错。

解决方法

方法1:类型断言(简洁直接)

直接通过类型断言告诉TS,返回的{a}就是M[T]类型:

function f1<T extends keyof M>(a: T): M[T] {
    return {a} as M[T];
}

需要确保代码逻辑正确,因为类型断言会跳过TS的类型检查。

方法2:利用as const固定字面量类型

通过as const让TS把{a}的类型推断为精确的字面量类型,再关联到M[T]:

function f1<T extends keyof M>(a: T): M[T] {
    return {a} as const as M[T];
}

as const会将{a}的类型锁定为{ readonly a: T },而T是keyof M的子类型,因此这个类型与M[T]完全匹配。

方法3:函数重载(类型最明确)

通过函数重载为每个key定义明确的参数和返回类型,TS会自动根据参数匹配对应的返回类型:

function f1(a: "foo"): M["foo"];
function f1(a: "bar"): M["bar"];
function f1(a: keyof M) {
    return {a};
}

这种方式不需要类型断言,类型检查最严格,同时完全满足动态创建实例的需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 06:34:51