DEV Community

David Johnston
David Johnston

Posted on

3

What's going on with index types in TypeScript?

Playground link for all code here.

In TypeScript you can reference the type of an object property using the square bracket notation.

eg:



type Foo = {
   a: string;
   b: number;
   1: null;
}

type A = Foo["a"]; //string 
type B = Foo["b"]; //number
type ObjOne = Foo[1]; //null; 

This also works for arrays


type MyArray = [string, number, string];

type Zero = MyArray[0]; //string 
type One = MyArray[1]; //number

Now according to the TypeScript documentation the keyof keyword returns a union type of all of those possible keys, which also includes the Array.prototype methods such as reduce, map etc.

type Reduce = MyArray["reduce"]; //type Reduce = {    (callbackfn: (previousValue: 0 | "one" | ..... 
type Length = MyArray["length"] //3

This works for ordinary objects too, but there aren't many useful native methods that exist on an object instance's prototype (Object.values etc exist as static functions on the class prototype):

type ToString = Foo["toString"]; //() => string
type Values = Foo["values"]; //Property 'values' does not exist on type 'Foo'.(2339)

Now where this gets interesting is that as well as referencing the types of properties via a string or number literal as an index - we can also reference them by a given type as an index. However, in this current example, it only works for the number type on arrays only:

type RefObjectByTypeNumber = Foo[number]; //Type 'Foo' has no matching index signature for type 'number'.(2537)
type RefObjectByTypeString = Foo[string]; //Type 'Foo' has no matching index signature for type 'number'.(2537)
type RefArrayByTypeNumber = MyArray[number]; //string | number
type RefArrayByTypeString = MyArray[string]; //Type 'MyArray' has no matching index signature for type 'string'.(2537)

If we declare an object by explicitly declaring the types of the keys, this does work:

type Bar = {
   [key: string]: string,
}; 
type RefBarByTypeString = Bar[string]; //string

type Chaz = {[key: number]: number}; 
type RefChazByTypeNumber = Chaz[number]; //number

If we declare the object keys directly as string literals, this does not work:

type Eep = {
   "a": number; 
}
type RefEepByKeyA = Eep["a"]; //number
type RefEepByTypeString = Eep[string]; // Type 'Eep' has no matching index signature for type 'string'.(2537)

It's not possible to declare both types as keys:

type Donk = {
   [keya: string]: string;
   [keyb: number]: number; //Numeric index type 'number' is not assignable to string index type 'string'.(2413)
}

Not that I necessarily want to do that.

So my main question here is - what's actually going on with how we can use types as an index - but only in specific circumstances?

The documentation from Typescript isn't particularly clear here - the only references in the documentation that I can find is is this part on index types:
https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types

which doesn't actual demonstrate using a type as an index,

and the example given here in the keyof documentation:

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

Top comments (2)

Collapse
 
macsikora profile image
Pragmatic Maciej

We have here three separated things.

1.Index type
Its a type accessor by index. So for example User['id'] take a type of property id from User type.
More about index type
2.Index signature type
Its a way to define the type, but limited to index type being string or number. More about that - index signatures

// Indexed type syntax only available for keys being string or number
type Donk = {
  [keya: string]: string;
   // no more rows available
}
const donk = { 'any_str': 'any_str' }; // string to string object
Enter fullscreen mode Exit fullscreen mode

3.Mapped types
Mapped type is a construction allows create a type by mapping through keys being another type. This is exactly the construct which allows on creating object types with specified keys and values types.

Mapped type example:

type Keys = 'a' | 'b' | 'c'
type Donky = {
   [K in Keys]: string
}
const donky: Donky = { a: 'a', b: 'b', c:'c' }; // a | b | c to string object
Enter fullscreen mode Exit fullscreen mode

More about mapped type

Collapse
 
abdmun8 profile image
Abdul Munim

thank you for sharing this article, this is a good reference for writing index types