typescripttypes

TypeScript type hinting similar to `document.createElement`


I am trying to create similar createElement method of my own custom DOM (with custom nodes) for my project, which takes nodeName as argument and returns an instance of corresponding Node.

import { Node } from "./tree";

export class DOM
{
    private defs: Record<string, new () => Node>;

    constructor(nodeDefs: Record<string, new () => Node>)
    {
        for (const [nodeName, NodePrototype] of Object.entries(nodeDefs))
            this.defs[nodeName] = NodePrototype;
    }

    create(nodeName: keyof typeof this.defs)
    {
        const newNode = new this.defs[nodeName]();
        newNode.name = nodeName;

        return newNode;
    }
}

The code works fine but I don't get handy hints of which node names are available and the return type is always just Node, not the exact type of node I am creating.

How should I change the code above to make hints work properly?

const dom = new DOM({
    paragraph: Paragraph,
    text: Text
});

const myP = dom.create('paragraph'); // Correct type hints here!

Solution

  • Let's make your DOM class and create method become generic:

    type InstanceTypeOf<T> = T extends new () => infer I ? I : never; // get type from a constructor
    
    export class DOM<T extends Record<string, new () => Node>> { // Genneric T, infer constructor param
      private readonly defs: T = {} as T;
    
      constructor(nodeDefs: T) {
        for (const [nodeName, NodePrototype] of Object.entries(nodeDefs) as Array<[keyof T, T[string]]>) { // fix typing error
          this.defs[nodeName] = NodePrototype;
        }
      }
    
      create<K extends Extract<keyof T, string>>(nodeName: K) { // generic, infer by nodeName type
        const newNode = new this.defs[nodeName]();
        newNode.name = nodeName;
    
        return newNode as InstanceTypeOf<T[K]>; // casting
      }
    }
    
    const dom = new DOM({
      paragraph: Paragraph,
      text: Text,
    });
    
    const p = dom.create('paragraph');
    //    ?^ const p: Paragraph