Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript generic argument comparison

Tags:

typescript

So there's this piece of code at the end of Typescript's generics guide

class BeeKeeper {
    hasMask: boolean = true;
}

class ZooKeeper {
    nametag: string = 'abc';
}

class Animal {
    numLegs: number = 123;
}

class Bee extends Animal {
    keeper: BeeKeeper = new BeeKeeper();
}

class Lion extends Animal {
    keeper: ZooKeeper = new ZooKeeper();
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

This program just creates some classes and then defines a method that creates an instance of a given class. A problem arises when we try to do comparisons with variable c though. For example, if we want to check whether c == Lion then Typescript throws an error(playground link). Anyone know a way to do such comparisons while still keeping type checking in place?

like image 295
Komninos Avatar asked Oct 02 '19 14:10

Komninos


2 Answers

You can make use of type guards.

It would look something like this:

type Constructor<T extends {} = {}> = new (...args: any[]) => T;

function typeGuard<T>(o: any, className: Constructor<T>): o is T {
  return o === className;
}

function createInstance<A extends Animal>(c: Constructor<A>): A {
    if (typeGuard(c, Lion)) {
        return new c('Lion initialized')
    }
    return new c();
}

Also please note the usage of Constructor<A> - you've mention that your constructors would receive parameters based on type - this can't be achieved by your implementation because your constructor definition doesn't receive any parameters and therefore you should use Constructor<A> to pass any number of parameters to constructor (it's up to you to pass correct arguments to correct type constructors).

Taking all that into account, I'd suggest a much cleaner and simpler approach. First define map of keys and corresponding types that can be created:

type TypeMap = {
    "lion": Lion,
    "bee": Bee
}

Then just create factory function which would suggest possible arguments ("lion" | "bee") on invoke, show error if unknown argument is entered and resolve correct return type.

function createSimpleInstance<T extends keyof TypeMap>(c: T): TypeMap[T] {
    let instance: Animal;
    switch (c) {
        case "lion":
            instance = new Lion("Lion initialized");
            break;
        case "bee":
            instance = new Bee();
            break;
        default:
            throw new Error("Unknown type");
    }
    return instance as TypeMap[T];
}

Great benefit of this is that within createSimpleInstance you are creating exact instances of Lion and Bee which helps you with autocomplete of exact arguments thats needed for each constructor function.

Please see playground.

like image 174
zhuber Avatar answered Nov 18 '22 16:11

zhuber


have you tried the following?
if(c instanceof Lion) {

Hope i understand your questions correctly.

like image 39
Paula Avatar answered Nov 18 '22 15:11

Paula