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 propertyidfromUsertype.More about index type
2.Index signature type
Its a way to define the type, but limited to index type being
stringornumber. 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