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:
- define a generic
T
- restrict
T
toHTMLElement
or subtypes thereof - define both the first parameter and return type as the generic
T
- use
document.createElement(tagName: string)
which returns typeHTMLElement
- make sure the return type of
createElement
is typeT
– I, the author, know it will be, because creating an element with the same tagName will always result in the same type of element, butcreateElement
doesn't know this because it doesn't know specifically what valueel.tagName
is, it only knows it's of typestring
.
/**
* @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
.
CodePudding user response:
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 sussienctly
function clone<T extends HTMLElement>(el: T): T {
return document.createElement(el.tagName) as T;
}