网站搜索

typescript - 如何使用条件类型在函数体内进行类型缩小


我定义了一个条件类型,以便我的类构造函数的第二个参数根据第一个参数的类型受到限制。

类型检查器可以正确识别我在调用构造函数时被允许的内容。然而,在我的构造函数内部,我无法让编译器推断类型。

enum Type {
    A = 'A',
    B = 'B',
}
type TypeKeys<T extends Type> =
    T extends Type.A ? {a: number} :
    T extends Type.B ? {b: number} : never;

class TestClass<T extends Type> {
    constructor(type: T, data: TypeKeys<T>) {
        if(type === Type.A) {
            data.a // Property 'a' does not exist on type 'TypeKeys<T>'.
        }
    }
}

new TestClass(Type.A, {a: 10})

//Argument of type '{ a: number; }' is not assignable to parameter of type '{ b: number; }'.
new TestClass(Type.B, {a: 10})

有没有办法在构造函数体内利用类型收窄?

答案#1

一种选择是使用判别联合。在两个接口上添加共享属性(类型为 Type 的 kind)。这样 TypeScript 就可以在构造函数体内和外部对类型进行“判别”。

enum Type {
    A = 'A',
    B = 'B',
}

interface A {
    kind: Type.A;
    a: number;
}

interface B {
    kind: Type.B;
    b: number;
}

class TestClass {
    constructor(data: A | B) {
        if(data.kind === Type.A) {
            data.a = 5;
        } else {
            data.a // Property 'a' does not exist on type 'TypeKeys<T>'.
        }
    }
}

new TestClass({ kind: Type.A, a: 10 })

//Argument of type '{ a: number; }' is not assignable to parameter of type '{ b: number; }'.
new TestClass({ kind: Type.B, a: 10 })

答案#2

我猜编译器还不够聪明,无法隐式地判断你的代码是安全的。我认为发生了以下情况:

当你创建类的新实例时,编译器会看到你的类型约束,并正确地阻止你传递错误的数据。然而,在你的构造函数内部,它似乎没有在两个不同的变量之间传播该约束。我想可能是因为如果你有多个变量控制你的数据类型,情况可能会变得非常复杂。

Typescript 确实有一种称为区分联合类型(discriminated unions)的功能,是为像这样的情况设计的,尽管根据个人经验,我发现自动类型推断有时会出错,在某些情况下根本无法工作。我已经放弃让它自动推断类型,所以我经常通过类型转换来使其工作:

if (data.type === Type.A) {
    const dataA = <A>data;
    dataA.a;
}