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