TypeScript knows that globalThis.document.createElement('img')
returns a type of HTMLImageElement
based on the string 'img'
being passed into the function.
const elem1 = globalThis.document.createElement('img'); //type: HTMLImageElement
console.log(elem1.constructor.name); //'HTMLImageElement'
How would I capture that return type to be used in a wrapper function?
For example, what would the TypeScript declaration be for the createElem
function below so that the tag
parameter determines the correct return type?
const createElem = (tag: keyof HTMLElementTagNameMap) => {
const elem = globalThis.document.createElement(tag);
elem.dataset.created = String(Date.now());
return elem;
};
const elem2 = createElem('img'); //type: HTMLElement | ...68 more...
console.log(elem2.constructor.name); //'HTMLImageElement'
A tag
value of ’img’
should result in a HTMLImageElement
type. A tag
value of ’p’
should result in a HTMLParagraphElement
type and so on.
If you check the official type definition of createElement
you will see:
createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
Let's apply the same logic to your function:
const createElem = <K extends keyof HTMLElementTagNameMap>(
tag: K,
): HTMLElementTagNameMap[K] => {
const elem = globalThis.document.createElement(tag);
elem.dataset.created = String(Date.now());
return elem;
};
Testing:
const elem2 = createElem('img'); // HTMLImageElement
const elem3 = createElem('p'); // HTMLParagraphElement
console.log(elem2.constructor.name);
Additionally, createElement
supports passing any other strings as well, which is achieved using function overloading:
createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K];
/** @deprecated */
createElement<K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K];
createElement(tagName: string, options?: ElementCreationOptions): HTMLElement;
You can do the same thing for your function if you need that. Note that the order of overloads is important. The most defined one should come first. Which is keyof HTMLElementTagNameMap
in your case. The reason is typescript looks through the overloads from top to bottom and if you put the overload with string
first then it would reach the keyof HTMLElementTagNameMap
.