【问题标题】:Provide a second generic type conditionally based on the first type基于第一种类型有条件地提供第二种泛型类型
【发布时间】:2021-09-16 20:39:13
【问题描述】:

我正在尝试为mongoose 生成一个类定义,我想使用第一个泛型来选择第二个泛型。简化的概念版本如下所示:

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 中无效,因此我想知道,如何使第二个泛型依赖于第一个参数?

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

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

    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,因为

    同一类型变量在逆变位置的多个候选导致推断出交集类型。

    因此string &amp; number === never

    考虑这个例子:

    
    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; } &amp; { 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

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

    【讨论】:

    • 很棒的答案!正如我在开头提到的,真实的例子涉及猫鼬,其中我有两种不同的模型类型,具体取决于基本类型——一种是常规模型,另一种是插件扩展模型。我想最后一个例子解决了这个问题,但它感觉像巫术,可能不推荐......
    • @MaxGordon 你是对的,我个人不能推荐这种方式。就像我说的,PROD 代码应该是conditional statements
    猜你喜欢
    • 1970-01-01
    • 2021-02-02
    • 1970-01-01
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多