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!
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