Can TypeScript infer `this` value correctly in child static methods? - TagMerge
4Can TypeScript infer `this` value correctly in child static methods?Can TypeScript infer `this` value correctly in child static methods?

Can TypeScript infer `this` value correctly in child static methods?

Asked 1 years ago
0
4 answers

The reason the snippet in question does not work is because the this value within A.create is only derived at runtime. Directly inferring it as its parent class is wrong because A.create can always be called with a totally unrelated this value:

A.create.call(String, 'abc');     // String { 'abc' }
A.create.bind(Boolean, 'abc');    // Boolean { true }
A.create.apply(Number, ['abc']);  // Number { NaN }

In other words, TS cannot know beforehand the type of this, hence inference of this value to the parent class is not possible.

This is why we will need type assertion and generics to explicitly tell TS what typing it should use (essentially telling the compiler to leave the guessing job to us).

class A {
  value: string;

  static create<T extends typeof A>(this: T, value: string): InstanceType<T> {
    return new this(value) as InstanceType<T>;
  }

  constructor(value: string){
    this.value = value;
  }
}

class B extends A {
  log(){
    console.log(this.value);
  }
}

const test1 = B.create('test1');

test1.log();

Playground

Source: link

0

Being new to TypeScript, what is the best method to implement a static factory in a base class that instantiates the child class type. For instance, consider a findAll method in a base model class:
class BaseModel {
  static data: {}[];
  static findAll() {
    return this.data.map((x) => new this(x));
  }
  constructor(readonly attributes) {
  }
}

class Model extends BaseModel {
  static data = [{id: 1}, {id: 2}];
  constructor(attributes) {
    super(attributes);
  }
}

const a = Model.findAll();  // This is BaseModel[] not Model[]
To answer my own question, this turns out to be a well known issue in TypeScript. The Github issue Polymorphic this for static methods has a long discussion. The solution is as follows:
export type StaticThis<T> = { new (): T };

export class Base {
    static create<T extends Base>(this: StaticThis<T>) {
        const that = new this();
        return that;
    }
    baseMethod() { }
}

export class Derived extends Base {
    derivedMethod() { }
}

// works
Base.create().baseMethod();
Derived.create().baseMethod();
// works too
Derived.create().derivedMethod();
// does not work (normal)
Base.create().derivedMethod();
This is an example of how it might look - each subtype defines its own static findAll() method that calls the standard behaviour on the parent class, passing the data and constructor along for the parent to use:
class BaseModel {
    static data: {}[];

    static _findAll<T extends BaseModel>(data: any[], Type): T[] {
        return data.map((x) => new Type(x));
    }

    constructor(readonly attributes) {
    }
}

class Model extends BaseModel {
    static data = [{ id: 1 }, { id: 2 }];

    constructor(attributes) {
        super(attributes);
    }

    static findAll() {
        return BaseModel._findAll(this.data, this);
    }
}

const a = Model.findAll();

Source: link

0

We’ve all been in situations where we used a library that had been typed sparingly. Take the following third-party function, for example:
function describePerson(person: {
  name: string;
  age: number;
  hobbies: [string, string]; // tuple
}) {
  return `${person.name} is ${person.age} years old and love ${person.hobbies.join(" and  ")}.`;
}
If the library doesn’t provide a standalone type for the person argument of describePerson, defining a variable beforehand as the person argument would not be inferred correctly by TypeScript.
const alex = {
  name: 'Alex',
  age: 20,
  hobbies: ['walking', 'cooking'] // type string[] != [string, string]
}

describePerson(alex) /* Type string[] is not assignable to type [string, string] */
And, even if it did, it would be nice to have type checking on the alex object itself to have proper autocompletion. We can easily achieve this, thanks to the infer keyword in TypeScript.
const alex: GetFirstArgumentOfAnyFunction<typeof describePerson> = {
  name: "Alex",
  age: 20,
  hobbies: ["walking", "cooking"],
};

describePerson(alex); /* No TypeScript errors */
StringFromType returns a literal string based on the primitive type it receives:
type StringFromType<T> = T extends string ? 'string' : never

type lorem = StringFromType<'lorem ipsum'> // 'string'
type ten = StringFromType<10> // never
To cover more cases for our StringFromType generic, we can chain more conditions exactly like nesting ternary operators in JavaScript.
type StringFromType<T> = T extends string
  ? 'string'
  : T extends boolean
  ? 'boolean'
  : T extends Error
  ? 'error'
  : never

type lorem = StringFromType<'lorem ipsum'> // 'string'
type isActive = StringFromType<false> // 'boolean'
type unassignable = StringFromType<TypeError> // 'error'

Source: link

0

To declare a static property, you use the static keyword. To access a static property, you use the className.propertyName syntax. For example:
class Employee {
    static headcount: number = 0;

    constructor(
        private firstName: string,
        private lastName: string,
        private jobTitle: string) {

        Employee.headcount++;
    }
}Code language: TypeScript (typescript)
The following creates two Employee objects and shows the value of the headcount property. It returns two as expected.
let john = new Employee('John', 'Doe', 'Front-end Developer');
let jane = new Employee('Jane', 'Doe', 'Back-end Developer');

console.log(Employee.headcount); // 2
Code language: TypeScript (typescript)
Similar to the static property, a static method is also shared across instances of the class. To declare a static method, you use the static keyword before the method name. For example:
class Employee {
    private static headcount: number = 0;

    constructor(
        private firstName: string,
        private lastName: string,
        private jobTitle: string) {

        Employee.headcount++;
    }

    public static getHeadcount() {
        return Employee.headcount;
    }
}Code language: TypeScript (typescript)
To call a static method, you use the className.staticMethod() syntax. For example:
let john = new Employee('John', 'Doe', 'Front-end Developer');
let jane = new Employee('Jane', 'Doe', 'Back-end Developer');

console.log(Employee.getHeadcount); // 2
Code language: TypeScript (typescript)

Source: link

Recent Questions on typescript

    Programming Languages