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)
We have here three separated things.
1.Index type
Its a type accessor by index. So for example
User['id']
take a type of propertyid
fromUser
type.More about index type
2.Index signature type
Its a way to define the type, but limited to index type being
string
ornumber
. More about that - index signatures3.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:
More about mapped type
thank you for sharing this article, this is a good reference for writing index types