Home > database >  How to use a generic type with `typeof Node<T>`?
How to use a generic type with `typeof Node<T>`?

Time:02-11

Let's say we have some Java Node, StringNode and NodeFactory classes:

public class Node<T> {
    private T value;

    public void init(T value){
        this.value = value;
    }
}

public class StringNode extends Node<String> {}

public class NodeFactory {
    public static <T> Node<T> createNode(Class<? extends Node<T>> nodeClass, T value) throws Exception {
        Node<T> node = nodeClass.getConstructor().newInstance();
        node.init(value);
        return node;
    }
}

So NodeFactory.createNode(StringNode.class, "value1"); would create a StringNode with the value: "value1".

How could I write such a factorymethod in typescript?

I tried to write a factorymethod in typescript:

public static createNode<T>(NodeClass: typeof Node<T>, value: T): Node<T> {
    const node = new NodeClass();
    node.init(value);
    return node;
}

but does not accept typeof Node<T> generic types. So I tried putting in the method's type arguments:

public static createNode<T, N extends Node<T>>(NodeClass: typeof N, value: T): Node<T> {

But the compiler does not accept that either: typeof N: TS2693: 'N' only refers to a type, but is being used as a value here

Is there a typesafe way to write such a factorymethod in typescript?

CodePudding user response:

The generic parameter of a class applies to to instances and not the static class itself. And the value is also for instances. So NodeClass needs to be a "newable" type, that returns an instance with the right generic.

That means this will work:

function createNode<T>(NodeClass: { new (): MyNode<T> }, value: T): MyNode<T> {
    const node = new NodeClass();
    node.init(value)
    return node;
}

Testing:

// good
const a = createNode(StringNode, "asd")
const b = createNode(NumberNode, 123)
const c = createNode(FunctionNode, () => 'hello world')
const d = createNode(MyNode, true) // MyNode<boolean>

// error, as expected
const bad = createNode(StringNode, 123)

createNode(MyNode, true) is a nice bonus. Typescript picks up T from the second argument, and puts it in the NodeClass for you.

Playground

CodePudding user response:

I found a solution:

public static createNode<N extends { new(): Node<T> }, T>(NodeClass: N, value: T): Node<T> {

apparently I just needed N to implement a interface with a new(): Node<T> method (or constructor).

  • Related