3

I have been playing with TypeScript and I ran into a problem I wrote a shorthand for querySelectorAll()

export function selectAll(DOMElement: string, parent = document): Array<HTMLElement> | null {
    return [...parent.querySelectorAll(DOMElement)];
}

The above code giving me Type 'Element[]' is not assignable to type 'HTMLElement[]'.

Then I changed my code a little bit

export function selectAll(DOMElement: string, parent = document): Array<HTMLElement> | null {
    return Array.from(parent.querySelectorAll(DOMElement));
}

Now I am not getting any error. So, my questions are -

  • Why Array<HTMLElement> didn't work but Array<Element> worked.
  • What should I use [...] spread operator or Array.from().

In Addition

Like bogdanoff mentioned in comment

"from docs querySelectorAll returns a non-live NodeList containing Element so Array<Element> is right type."

Then why querySelector is okay with returning HTMLElement instead of just Element

export function selectAll(DOMElement: string, parent = document): HTMLElement | null {
    return parent.querySelector(DOMElement);
}
3
  • 1
    from docs querySelectorAll returns a non-live NodeList containing Element so Array<Element> is right type. Commented Apr 1, 2022 at 6:20
  • 1
    I'd think {...} refers to object spread and not array spread which would maybe be better rendered as [...]. Commented Apr 1, 2022 at 14:27
  • @jcalz Thanks for the suggestion I changed the title Commented Apr 1, 2022 at 17:32

2 Answers 2

2

what happens behind the hoods

In order to understand what is going on, we need to look at how typescript defines the signature for Array.from and querySelectorAll. Excerpts:

from<T>(arrayLike: ArrayLike<T>): T[];
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;

For Array.from, it means that, given a type T, it will return an array of elements with type T. If no T is given, it could be anything.

Specifying a type for the return value of the function is actually saying: "please take T = HTMLElement". In turn, that implies the argument arrayLike will have type ArrayLike<HTMLElement>. NodeListOf<E> will match this by taking E = HTMLElement, which does extend Element, so it is valid, and typescript is happy.

why does typescript behave so?

Because you are deliberately giving the HTMLElement type for the return value, typescript trusts you and is capable to give specific types to E and T.

to go beyond

Interestingly, the following code would raise a typescript error:

function selectAll(DOMElement: string, parent = document): Array<HTMLElement>  {
  const all = parent.querySelectorAll(DOMElement);
  return Array.from(all);
}

This is because on the line defining all, there is no information whatsoever about what type E could be, so it defaults to being Element. When parsing the return line, TS will (rightly) complain that Element cannot map to HTMLElement.

Conclusion

To your questions:

  • use spread operator or Array.from => in your case, spread operator is not an option since it raises an error, which Array.from does not
  • why querySelector is okay with returning HTMLElement instead of just Element? => hopefully, the above has clarified why
Sign up to request clarification or add additional context in comments.

Comments

0

I played around with the snippet you've provided and I think Array.from casts Element[] into HTMLElement[]

function selectAll(DOMElement: string, parent = document): Array<HTMLElement> | null {
    const m : Array<HTMLElement> | null=  Array.from(parent.querySelectorAll(DOMElement));
    return m
}


function selectAll2(DOMElement: string, parent = document): Array<HTMLElement> | null {
    const m =  Array.from(parent.querySelectorAll(DOMElement));
    return m
}

If you check selectAll2 will throw an error.

And this won't work in


const arr = ["a","b"];

const setup1 = () : number[] => {
    return [...arr]
}

const setup1 = () : number[] => {
    return Array.from(arr)
}

cause HTMLElement is a child class of Element[] itself.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.