基于第一种类型有条件地提供第二种泛型类型

Provide a second generic type conditionally based on the first type

我正在尝试为 mongoose 生成一个 class 定义,其中我想使用第一个泛型到 select 第二个泛型。一个简化的概念版本看起来像这样:

class StringClass {
    val: string;

    constructor(val: string) {
        this.val = val;
    }
}

class NumberClass {
    val: number;

    constructor(val: number) {
        this.val = val;
    }
}

class test<A extends string | number, B extends typeof StringClass | typeof NumberClass) {
    val: B;
    
    constructor(val: A, Class: B) {
        this.val = new Class(val);
    }
}

我想将测试定义为:

class test<A extends string | number, B extends (A instanceof string ? typeof StringClass : typeof NumberClass)) {
    val: B;
    
    constructor(val: A, Class: B) {
        this.val = new Class(val);
    }
}

这在 Typescript 4.4 中无效,因此我想知道,如何使第二个泛型依赖于第一个参数?

为什么会出现这个错误? 这里有一个例子:

class StringClass {
    val: string;

    constructor(val: string) {
        this.val = val;
    }
}

class NumberClass {
    val: number

    constructor(val: number) {
        this.val = val;
    }
}

type Check<A> = A extends string ? typeof StringClass : typeof NumberClass

class test<A extends string | number, B extends Check<A>> {
    val: B;
    constructor(val: A, Class: B) {
        // Type 'string' is not assignable to type 'never'
        this.val = new Class(val);
    }
}

const result = new test('2', StringClass)

在上述情况下 val 推断为 never,因为

multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.

因此string & number === never

C考虑这个例子:


class StringClass {
    val: { string: string };

    constructor(val: { string: string }) {
        this.val = val;
    }
}

class NumberClass {
    val: { number: number }

    constructor(val: { number: number }) {
        this.val = val;
    }
}

type Check<A> = A extends string ? typeof StringClass : typeof NumberClass

class test<A extends string|number, B extends Check<A>> {
    val: B;
    constructor(val: A, Class: B) {
        // Type 'string' is not assignable to type '{ string: string; } & { number: number; }'
        this.val = new Class(val);
    }
}

const result = new test('2', StringClass)

val{ string: string; } & { number: number; }.

那么,如何解决呢?

您可以摆脱泛型并通过适当的限制重载您的构造函数



class StringClass {
    val: string;

    constructor(val: string) {
        this.val = val;
    }
}

class NumberClass {
    val: number

    constructor(val: number) {
        this.val = val;
    }
}

type Check<A> = A extends string ? typeof StringClass : typeof NumberClass

interface Overloading {
    new(val: string): any
    new(val: number): any
    new(val: string | number): any
}

class test {
    val: StringClass | NumberClass
    constructor(val: number, Class: typeof NumberClass)
    constructor(val: string, Class: typeof StringClass)
    constructor(val: never, Class: typeof StringClass & typeof NumberClass) {
        this.val = new Class(val)

    }
}

const _ = new test('2', StringClass) // ok
const __ = new test(2, StringClass) // expected error

Playground

一般来说,运行时值不能依赖于通用条件(参见 Check)。不安全。

这就是为什么在这里:



class StringClass {
    val: string;

    constructor(val: string) {
        this.val = val;
    }
}

class NumberClass {
    val: number

    constructor(val: number) {
        this.val = val;
    }
}

class test<A, B extends {
    0: typeof StringClass,
    1: typeof NumberClass,
    2: never
}[A extends string ? 0 : A extends number ? 1 : 2]> {
    val: B
    constructor(val: A, Class: B) {
        this.val = new Class(val) // error

    }
}

你有一个错误。

在这种情况下,您始终可以使用类型断言 as never


class StringClass {
    val: string;

    constructor(val: string) {
        this.val = val;
    }
}

class NumberClass {
    val: number

    constructor(val: number) {
        this.val = val;
    }
}

class test<A, B extends {
    0: typeof StringClass,
    1: typeof NumberClass,
    2: never
}[A extends string ? 0 : A extends number ? 1 : 2]> {
    val: StringClass | NumberClass
    constructor(val: A, Class: B) {
        this.val = new Class(val as never) // type asserion

    }
}

const _ = new test('2', StringClass) // ok
const __ = new test(2, StringClass) // expected error

在这里使用类型断言安全吗?我不确定。我认为这取决于你。如果此代码仅用于测试 - 请像我一样使用 as nevernever

如果这是生产代码 - 请使用条件语句