I'm writing a clone(el)
function that must accept an HTMLElement
or subtype, and should return the exact same type that it's given.
How can I write this function's generic signature to be correct please? Thanks!
Is no.2 where I'm going wrong, in how T
is being restricted to HTMLElement
?
To be precise, I'm writing plain JavaScript and using JSDoc in VS Code, which offers a @template
definition to define a generic:
T
T
to HTMLElement
or subtypes thereofT
document.createElement(tagName: string)
which returns type HTMLElement
createElement
is type T
– I, the author, know it will be, because creating an element with the same tagName will always result in the same type of element, but createElement
doesn't know this because it doesn't know specifically what value el.tagName
is, it only knows it's of type string
./**
* @template {HTMLElement} T
* @param {T} el
* @returns {T}
*/
function clone(el) {
/** @type {T} */
const newEl = document.createElement(el.tagName);
// ^^^^^ Error from above appears on this
// do copying of attributes and child nodes
return newEl;
}
I believe this to be the same result in TypeScript:
function clone<T extends HTMLElement>(el: T): T {
const newEl: T = document.createElement(el.tagName);
// do copying of attributes and child nodes
return newEl;
}
However, TypeScript complains that
Type 'HTMLElement' is not assignable to type 'T'.
'HTMLElement' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'HTMLElement'. (2322)
The error makes sense to me, because if I rewrite it for a specific example, I get the same error, but more understandable:
function clone<T extends HTMLElement>(el: T): T {
return document.createElement('input');
}
Type 'HTMLInputElement' is not assignable to type 'T'.
'HTMLInputElement' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'HTMLElement'. (2322)
I've tried to read the answer in https://stackoverflow.com/a/59363875 but I feel like, in my case, I'm more restricted because document.createElement
is defining its type signature as Document.createElement(tagName: string, options?: ElementCreationOptions | undefined): HTMLElement
.
The issue you're experiencing is because document.createElement returns an HTMLElement, which is a supertype of the type parameter T. This means that the return is not guaranteed to have the same type as el, and therefore you get a type error when trying to assign it to a variable of type T.
Your final function is fairly close, you just need to make the return as type T.
function clone<T extends HTMLElement>(el: T): T {
const newEl = document.createElement(el.tagName);
return newEl as T;
}
or more succinctly
function clone<T extends HTMLElement>(el: T): T {
return document.createElement(el.tagName) as T;
}