Home > Net >  TypeScript: Is it possible to mix a static property of one class with another class without using in
TypeScript: Is it possible to mix a static property of one class with another class without using in

Time:12-26

I'd like that Intellisense of TypeScript recognize a static property inherited through a interface, without apply inheritance or implementation in a concrete class. Is it possible?


interface Interface2 {

    instancePropertyRecognizedByInterfaceInheritance: string;

}

class Interface1 {

    constructor() {

    }

    instanceMethodRecognizedByInterfaceInheritance(): void {

    }

}

class InheritsFromAll {

    static inheritedClassProperty: string = `class property defined in ${this.name}`;

}

// interfaces permit multiple inheritance even from another class
interface InheritsFromAll extends 
    Interface1,
    Interface2
    {

}

// it works only for instance properties
interface MixedClass extends InheritsFromAll {

}

class MixedClass {
    
}

const mixedClass: MixedClass = new MixedClass();

// recognized
mixedClass.instanceMethodRecognizedByInterfaceInheritance();

// not recognized
MixedClass.inheritedClassProperty;

CodePudding user response:

To answer the question as asked, there is currently no way to use declaration merging to modify the static side of a class. There's a longstanding open feature request at microsoft/TypeScript#2957 asking for a way to do this, but for the foreseeable future, there are only workarounds. The one mentioned in that issue is to declare a namespace/module with the same name as the class constructor, and export statics individually:

namespace MixedClass {
  export const inheritedClassProperty = InheritsFromAll.inheritedClassProperty;
}
const mixedClass: MixedClass = new MixedClass();

MixedClass.inheritedClassProperty;

For some people, that works well enough for their use cases.


If it doesn't work for you, the best approach would be to step back and try to think of the underlying use case and whether or not there is a solution that doesn't involve declaration merging at all. If you just want to use the statics of one class as a mixin for another class, you can maybe make a function which takes both the mixin and the target class constructor and returns the target class constructor as an intersection of both the original constructor and the static part of the mixin constructor, like this:

const MixStatics = <T, U>(mixin: T, ctor: U) => Object.assign(ctor, mixin) as
    U & { [K in keyof T as Exclude<K, "prototype">]: T[K] };

This produces a type like U & T, where U is the type of the target class constructor ctor, and T is the type of the mixin statics. But we don't want to copy the prototype property or the construct signature from T into the output, since the Object.assign() function won't copy those. Hence the key-remapped type.

Let's test it out:

class StaticsToMixIn {
    static staticProp: string = `class property defined in ${this.name}`;
    static prop1 = 1;
    static prop2 = 2;
    /*..*/
    static prop100 = 100;
}

const MixedClass = MixStatics(StaticsToMixIn, class MixedClass {
    static anotherStaticProp: string = `class property defined in ${this.name}`;
});

/* const MixedClass: typeof MixedClass & {
    staticProp: string;
    prop1: number;
    prop2: number;
    prop100: number;
} */

type MixedClass = InstanceType<typeof MixedClass>

According to the compiler, MixedClass is a class constructor of the same type as the class expression passed as the second argument of MixStatics, as well as an object type containing all the statics from StaticsToMixin.

And things seem to work similarly at runtime:

console.log(MixedClass.staticProp) // "class property defined in StaticsToMixIn" 
console.log(MixedClass.prop1) // 1
console.log(MixedClass.prop2) // 2
/*..*/
console.log(MixedClass.prop100) // 100
console.log(MixedClass.anotherStaticProp); // "class property defined in MixedClass" 

Is MixStatics() perfect? Probably not; there are likely many edge cases when you copy things from one constructor to another, both at runtime and in the type system. But at least this has the potential to programmatically merge the static side of classes together in a way that declaration merging currently cannot.

Playground link to code

  • Related