打字稿中的条件类型

时间:2021-05-05 14:27:47

标签: typescript types

假设我有一个要传递参数的函数。我自己的条件类型是基于参数的类型,但是当我想基于接口返回值时,函数给我一个错误。

这是实际代码:

interface IdLabel {
  id: number;
}

interface NameLabel {
  name: string;
}

type NameOrId<T extends string | number> = T extends number ? IdLabel : NameLabel;

function createLabel<T extends string | number>(idOrName: T): NameOrId<T> {
  if (typeof idOrName === "number") {
    return {
      id: idOrName
    };
  } else {
    return {
      name: idOrName
    };
  }
}

const a = createLabel(1);
const b = createLabel("something");

您可以在这张图片中看到错误:

enter image description here

这是文本错误:

Type '{ id: T & number; }' is not assignable to type 'NameOrId<T>'.
Type '{ name: T; }' is not assignable to type 'NameOrId<T>'.

1 个答案:

答案 0 :(得分:2)

NameOrId<T> 这样的条件类型依赖于 T 主体内的 createLabel() 这样的未解析类型参数时,编译器推迟对其进行评估。这样的类型对编译器来说本质​​上是不透明的,因此它通常无法验证像 {id: idOrName} 这样的特定值是否可以分配给它。

当您选中 typeof idOrName 时,control flow analysis 会缩小 idOrName 的明显类型,但这不会缩小类型参数 T 本身.如果 typeof idOrName === "string"idOrName 现在被称为 string(或 T & string)类型,但 T 仍然可能是 string | number,并且是仍然是未解析的泛型类型参数。

这是 TypeScript 的一个已知痛点。有一个悬而未决的问题 microsoft/TypeScript#33912,要求某种方法来允许控制流分析验证未解析条件类型的可分配性,尤其是作为您想要的函数的返回值。除非该问题得到解决,否则您将不得不解决它。


解决方法:

每当您确定表达式 expr 的类型为 Type 而编译器不是类型时,您始终可以使用 type assertion 告诉编译器: expr as Type,或者,在编译器认为 expr 类型与 Type 完全无关的情况下,您可能必须编写 expr as any as Type 或使用其他一些中间断言。

那会给你这个:

function createLabelAssert<T extends string | number>(idOrName: T): NameOrId<T> {
    if (typeof idOrName === "number") {
        return {
            id: idOrName
        } as unknown as NameOrId<T>;
    } else {
        return {
            name: idOrName
        } as unknown as NameOrId<T>;
    }
}

这解决了错误。请注意,通过进行断言,您对类型安全负责。如果您将 typeof idOrName === "number" 检查修改为 typeof idOrName !== "number",仍然不会出现编译器错误。但是你会在运行时不开心。


当您有一个无法在实现中验证其返回类型的函数时,您可以执行类型断言的“道德等效”:具有单个调用签名的 overload。重载实现的检查比常规函数更松散,因此您可以获得与类型断言相同的行为,而无需在每个 return 行分别进行断言:

// call signature
function createLabel<T extends string | number>(idOrName: T): NameOrId<T>;
// implementation
function createLabel(idOrName: string | number): NameOrId<string | number> {
    if (typeof idOrName === "number") {
        return {
            id: idOrName
        };
    } else {
        return {
            name: idOrName
        };
    }
}

调用签名是相同的,但现在实现签名只是从 string | numberIdLabel | NameLabel 的映射。同样,编译器不会发现您不小心检查 typeof idOrName !== "number" 的问题,因此您在这里也需要小心。

Playground link to code