首页 > 解决方案 > 如何让通用工厂对不同类的构造函数进行类型检查?

问题描述

给定这个例子:

    class OneArgConstructorClass {
        constructor(name: string) {}
    }
    class TwoArgConstructorClass {
        constructor(name: string, age: number) {}
    }
    class Creator<T>
    {
        constructor(tConstructor: new(...args: any[])=> T) {
            const it: T = new tConstructor();
        }
    }

    const e1 = new Creator<OneArgConstructorClass>(OneArgConstructorClass); 
    const e2 = new Creator<OneArgConstructorClass>(TwoArgConstructorClass); 

为什么 e1 和 e2 是有效的陈述?typescript 不应该抱怨没有正确数量和类型的参数就无法正确构造 e1/e2 吗?是否可以根据传入的类构造函数对该示例进行正确的类型检查?

标签: typescripttypescript-generics

解决方案


没有错误的原因是您的tConstructor参数采用任意数量的任何类型的参数。

如果您想更严格地限制允许哪些参数,您可以使用元组类型来准确指定哪些参数tConstructor需要,例如:

class Creator<T>
{
    constructor(tConstructor: new (...args: [string]) => T) {
    //                                      ^^^^^^^^
    // note that args is a tuple containing exactly one item, namely a string
        const it: T = new tConstructor();
        //            ^^^^^^^^^^^^^^^^^^
        // This is now wrong because the constructor takes a string as parameter
    }
}

const e1 = new Creator<OneArgConstructorClass>(OneArgConstructorClass);
const e2 = new Creator<OneArgConstructorClass>(TwoArgConstructorClass);
//                                             ^^^^^^^^^^^^^^^^^^^^^^^
// This is now also wrong because the constructor signatures are not compatible

如果你做了...args' 的签名[string, number],那么你的调用new tConstructor()仍然是错误的,因为你没有给它它的 2 个必需参数,但是new Creator<OneArgConstructorClass>(TwoArgConstructorClass)它现在接收到它正确的 2 个参数就可以了。

与您可能期望的不同,new Creator<OneArgConstructorClass>(OneArgConstructorClass)它也不再抛出错误,因为接受 1 个参数的构造函数将安全地忽略它接收到的任何附加参数。

希望这可以帮助。

编辑

如果您想要一种调用类构造函数的类型安全方式,您需要执行以下操作:

// Note the constraint that T must be "new"able
class Creator<T extends new (...args: any[]) => any>
{
    // ConstructorParameters is a builtin TS utility that when given a class returns the type of its parameters in a tuple
    // Note that we need to pass the actual parameters in somehow; in this example I've made Creator#constructor take 2 parameters:
    // 1) the class
    // 2) its constructor parameters in a tuple
    constructor(tConstructor: new (...args: ConstructorParameters<T>) => any,
        argsArray: ConstructorParameters<T>) {

        // Construct the class with the arguments array!
        const it: T = new tConstructor(...argsArray);
    }
}

// This will now work
const e1 = new Creator<typeof OneArgConstructorClass>(OneArgConstructorClass, ['hello']);

// This will now *not* work, which is correct, as the constructor signatures don't match
const e2 = new Creator<typeof OneArgConstructorClass>(TwoArgConstructorClass, ['hello', 14]);
//                                                    ^^^^^^^^^^^^^^^^^^^^^^
// Argument of type 'typeof TwoArgConstructorClass' is not assignable to parameter of type 'new (name: string) => any'.

// This *will* work because the signatures match
const e3 = new Creator<typeof TwoArgConstructorClass>(TwoArgConstructorClass, ['hello', 14]); 

推荐阅读