Call constructor on TypeScript class without new

后端 未结 6 948
醉话见心
醉话见心 2020-12-13 13:50

In JavaScript, I can define a constructor function which can be called with or without new:

function MyCl         


        
6条回答
  •  庸人自扰
    2020-12-13 14:19

    TL;DR

    If you are targeting ES6 and you really want to use class to store your data, not a function:

    • Create a function that simply invokes your class constructor with its arguments;
    • Set that function's prototype to the prototype of your class.

    From now you are able to call that function either with or without new keyword to generate new class instances.

    Typescript playground


    Typescript provides an ability to create such a function (let's call it a "callable constructor") in a strongly typed way. Well, any type is necessary in intermediate type definitions (replacing it with unknown causes errors), but this fact will not affect your experience.

    First of all we need to define basic types to describe entities we are working with:

    // Let's assume "class X {}". X itself (it has type "typeof X") can be called with "new" keyword,
    // thus "typeof X" extends this type
    type Constructor = new(...args: Array) => any;
    
    // Extracts argument types from class constructor
    type ConstructorArgs =
        TConstructor extends new(...args: infer TArgs) => any ? TArgs : never;
    
    // Extracts class instance type from class constructor
    type ConstructorClass =
        TConstructor extends new(...args: Array) => infer TClass ? TClass : never;
    
    // This is what we want: to be able to create new class instances
    // either with or without "new" keyword
    type CallableConstructor =
      TConstructor & ((...args: ConstructorArgs) => ConstructorClass);
    

    The next step is to write a function that accepts regular class constructors and creates corresponding "callable constructors".

    function CreateCallableConstructor(
        type: TConstructor
    ): CallableConstructor {
        function createInstance(
            ...args: ConstructorArgs
        ): ConstructorClass {
            return new type(...args);
        }
    
        createInstance.prototype = type.prototype;
        return createInstance as CallableConstructor;
    }
    

    Now all we have to do is to create our "callable constructor" and check it really works.

    class TestClass {
      constructor(readonly property: number) { }
    }
    
    const CallableTestConstructor = CreateCallableConstructor(TestClass);
    
    const viaCall = CallableTestConstructor(56) // inferred type is TestClass
    console.log(viaCall instanceof TestClass) // true
    console.log(viaCall.property) // 56
    
    const viaNew = new CallableTestConstructor(123) // inferred type is TestClass
    console.log(viaNew instanceof TestClass) // true
    console.log(viaNew.property) // 123
    
    CallableTestConstructor('wrong_arg'); // error
    new CallableTestConstructor('wrong_arg'); // error
    

提交回复
热议问题