Home > front end >  Typescript type narrowing based on class instance
Typescript type narrowing based on class instance

Time:02-10

I have Person class which implements interface IPerson. The interface were to have two variants: IPerson1 | IPerson2

interface IPerson {
    name: string | "all",
    age?: number,
    height?: number,
    population?: number,
}

interface IPerson1 {
    name: string,
    age: number,
    height: number,
}
interface IPerson2 {
    name: "all",
    population: number
}

class Person implements IPerson {
    name: string;
    age?: number|undefined;
    height?: number|undefined;
    population?: number|undefined;
    constructor(name: "all", population: number);
    constructor(name: string, age: number, height: number);
    constructor(name: string, ageOrPopulation: number, height?: number) {
        this.name = name;
        if (height) {
            this.age = ageOrPopulation;
            this.height = height;
        } else {
            this.population = ageOrPopulation;
        }
    }
}

new Person("all", 3e6).population // I hope here population is number instead of number | undefined
// I hope Person can implement both interface IPerson1 and IPerson2, and typescript can do type narrowing for me.

It's worth to mention, in my real code, Person class has so many methods, so I have to use class for good code structuring.

Is there any workaround for this scenario? Seems like if class can implments type, then I can make it implement a union type so TS can do type narrowing for me. playground

CodePudding user response:

You can't directly annotate the type of a constructor. You can however change the type of the class my assigning it to another variable and asserting a different type. With this technique you can then have different constructor signatures returning different types:

interface IPerson { name: string, age?: number, height?: number, population?: number, }
interface IPerson2 { name: "all", population: number }

class Person implements IPerson {
    age?: number|undefined;
    height?: number|undefined;
    population?: number|undefined;
    constructor(public name: string, ageOrPopulation: number, height?: number) {
        ///...
    }
}
const PersonCtor = Person as {
    new (name: "all", population: number): IPerson2
    new (name: string, age: number, height: number): IPerson
} 

new PersonCtor("all", 3e6).population // I hope here population is number instead of number | undefined

Playground Link

  • Related