Search code examples
typescripttype-inferencetypescript-class

How to get type inference using a utility type, or type parameter, when I extend the built-in array class?



Defining types for Class-member's in an Extended Array Class


So below, you can see that I extend the array class via the WillyWonka class. The problem is, when I do it as shown below, I get no type inference for the items stored in the array.

My biggest concern is that the array prototype implements exotic properties, making it a special, and somewhat unique, class to extend. I am extending it the same way I would in JS, and add the values to the Array using Object.assign(this, sumOmpaLoompas);. There are other ways to add items into the array, which I tried, but type inference was not gained. This is why I feel like I should be using a type parameter or utility type.


/*
---------------("DOCUMENT #1")--------------- */
type OmpaLoompa = {
  name: string,
  work: ()=> string
}

type OmpaLoompas = OmpaLoompa[];


class WillyWonka extends Array{
  constructor(sumOmpaLoompas: OmpaLoompas){
    super();
    Object.assign(this, sumOmpaLoompas);
  }
}

const ompaLoompas = [
  {
    name: 'Frank the Frightened Ompa',
    work: ()=> 'Produced Chocolate'
  },
  {

    name: 'Fran the Frail Loompa',
    work: ()=> 'Produced Carmel'
  },
  {
    name: 'Larry the Lazy Ompa',
    work: ()=> 'Didn\'t Produce Today'
  },
  {
    name: 'Linda the Lovely Loompa',
    work: ()=> 'Produced Wafers'
  }
];
/*


---------------("DOCUMENT #2")--------------- */
const wonkyWilly = new WillyWonka(ompaLoompas);
const ompaLoompaAtZero = wonkyWilly[0];
const ompaLoompaAtTwo = wonkyWilly[2];

console.log(ompaLoompaAtZero); //       <-- TYPE = ANY (I need this to be "type: OmpaLoompa")
console.log(ompaLoompaAtTwo.work()); // <-- TYPE = ANY (I need this to be "type: OmpaLoompa.work")

// EXPECTED OUTPUT:
// { name: 'Frank the Frightened Ompa', work: [Function: work] }
// Didn't Produce Today


SUMMARIZING


  1. In the example above there is a class WillyWonka that extends the Array class.

  2. The classes constructor takes a single argument of a well defined type.

  3. Then Object.assign(...) is used to add the iteratable objects from the constructors argument, as properties to the class, so the properties can be accessed, and iterated through using the extended array class.


Here's My Problem


Maybe I am writing the class wrong, to be honest I don't really know if this is the best way to go about defining a custom array object. It seems to work for all intents and purposes, except for one — and being that it is TypeScript, its sort of a big one — Type Inference. I was hoping that the TSC could infer the types so that when I import the WillyWonka class from one document to the next, it would know that willyWonka[0] was of the type OmpaLoompa, but TSC just annotates it as being any. If this wasn't an API that others will use, I probably would just deal with it, but I have to assume that others will use this Lib/API. So, at this point, if you have kept up with me, then my question should be pretty straight forward.

"How can I define the types for the properties on the custom WillyWonka extended array class?"

I am open to using another mechanism than Object.assign(...), so long as I can still access the OmpaLoompa typed properties as willyWonka[i:number]


Oh one other detail, I want the classes to be inferred to the imported class, in-other-words;

Do you see the comment that says, "/* DOCUMENT #1 */", and the other that says, "/* DOCUMENT #2 */"? I can't be coerced into typing the array's properties in document #2, because document two is an example of the Lib/API being used, the only part I will write is document #1. I need the types already defined when any arbitrary developer writes document #2 using the WillyWonk API.



I am pr


Solution

  • Simple fix: just add OmpaLoompa to the generic parameter of Array.

    class WillyWonka extends Array<OmpaLoompa> { /* ... */ }
    

    Otherwise TypeScript will assume that the elements of Array are of type any.