简体   繁体   中英

How to write a static method that returns a subclass instance

I'm trying to create an abstract class Enum , which has a bunch of static methods that return instances of the class they were called on. My problem is that I can't figure out how to type these methods correctly.

Essentially, I want TypeScript to understand that EnumSubclass.getFirstInstance() returns a EnumSubclass instance, and not an Enum instance.

Here is my best attempt so far:

type InstanceOf<T> = T extends { prototype: infer R } ? R : never

const instances = {}

abstract class Enum {
    protected constructor(
        public readonly name: string,
    ){
        instances[name] = this
    }

    public static getInstances<T extends typeof Enum>(this: T): InstanceOf<T>[] {
        return Object.values(instances)
    }

    public static getFirstInstance<T extends typeof Enum>(this: T): InstanceOf<T> {
        // No warning here, TypeScript understands that this.getInstances exists
        return this.getInstances()[0]
    }
}


class Demo extends Enum {
    public static readonly FOO = new Demo('foo', true)

    protected constructor(
        name: string,
        public readonly bar: boolean,
    ){
        super(name)
    }
}

// The 'this' context of type 'typeof Demo' is not assignable to method's 'this' of type 'typeof Enum'.
//   Types of construct signatures are incompatible.
//     Type 'new (name: string, bar: boolean) => Demo' is not assignable to type 'abstract new (name: string) => Enum'.ts(2684)
const x: Demo = Demo.getFirstInstance()

Apparently TypeScript has a problem with the fact that the constructor of Demo has a different signature than the constructor of Enum , even though Enum never even calls the constructor - it returns instances that already exist.

How can I convince TypeScript that this is not an error?

PS: Ideally, I would like to:

  1. Keep the constructors protected and not make them public
  2. Have correct typing inside the implementation of Enum (for example, there shouldn't be a warning at this.getInstances() )

That's long awaited feature that has yet to be implemented. #5863

So not possible at the moment.

This is possible, as long as you don't care about the constructor.

First, define these helper types:

type InstanceOf<T> = T extends { prototype: infer R } ? R : never
type IgnoreConstructor<T> = Pick<T, keyof T> & {name: string}

// Replace `Enum` with your base class
type EnumSubclass = IgnoreConstructor<typeof Enum>

Then write your static methods like this:

static myFunc<T extends EnumSubclass>(this: T): InstanceOf<T>

Proof that it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM