Home > Mobile >  What should I use Array.from or spread operator {...} in Typescript
What should I use Array.from or spread operator {...} in Typescript

Time:04-01

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);
}

CodePudding user response:

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.

CodePudding user response:

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.

  • Related