keyof を使うと、オブジェクトのすべてのキーを、文字列リテラル型の合併として取得できます。
あるtypeのキーを合併の型として、新たにtype宣言や型の制約をする場合に使用。
基本
type Test = {
    a: number, 
    b: string, 
    c: boolean
}
//testKeyofはTestのキー「'a' | 'b' |'c'」の型になる
type testKeyof = keyof Test
let a:testKeyof = 'a' //OK
let b:testKeyof = 'b' //OK
let c:testKeyof = 'c' //OK
let d:testKeyof = 'd' //エラー入れ子
オブジェクトが入れ子の場合。
type Test = {
    a: {
        b: {
            b2: string
        }
        c: {
            c2: number
        }
    }
}
type testKeyof = keyof Test //型は「a」
let a:testKeyof = 'a' //OK
type testKeyof2 = keyof Test['a'] //型は「'b' | 'c'」
let b:testKeyof2 = 'b' //OK
let c:testKeyof2 = 'c' //OK
type testKeyof3 = keyof Test['a']['b'] //型は「'b2'」
let b2:testKeyof3 = 'b2' //OK
type testKeyof4 = keyof Test['a']['c'] //型は「'c2'」
let c2:testKeyof4 = 'c2' //OK応用
type Test = {
    a: number
    b: string
    c: boolean
}
function sam<O extends object, K extends keyof O>(o: O, k: K): O[K] {
    return o[k]
}ジェネリック型のOはobjectの型でなければいけません。
Oを型Testと想定してみます。すると「keyof O」はTestの全てのキーの合併「’a’ | ‘b’ |’c’」になります。
これでKは型「’a’ | ‘b’ |’c’」のサブタイプである事がわかります。
仮にKが「a」であるならば戻り値O[K]はTest[‘a’]であり戻り値は「number」になります。
例1
function sam<O extends object, K extends keyof O>(o: O, k: K): O[K] {
    return o[k]
}
type Test = {
    id: number
    name: string
    options?: {
        type: 'men' | 'women'
        timestamp: Date
    }
}
let TestLog: Test = {
    id: 10,
    name: 'tanaka',
    options: {
        type: 'men',
        timestamp: new Date
    }
}
let testOptions = sam(TestLog, 'options') 
console.log(testOptions) //{ type: 'men', timestamp: 2020-06-22T19:22:33.201Z }ジェネリクスの型はtypescriptが推論します。
sam関数の第1引数の「o」にオブジェクト「TestLog」を渡したので、ジェネリクス型「O」をTest型と推論します。
その場合「keyof O」は「’id’|’name’|’options’」になり、ジェネリクスの型Kは「’id’|’name’|’options’」のサブタイプになります。
sam関数の第2引数の「k」に文字列「’options’」を渡したので、戻り値o[k]はTestLog[‘options’]の示す値になります。
例2
type Test = {
    id: number
    name: string
    options: {
        type: 'men' | 'women'
        timestamp: Date
    }[]
}
let TestLog: Test = {
    id: 10,
    name: 'tanaka',
    options: [{
        type: 'men',
        timestamp: new Date
    }]
}
type Get = {
    //①
    <O extends object, K1 extends keyof O>(o: O, k1: K1): O[K1] 
    //②
    <O extends object, K1 extends keyof O, K2 extends keyof O[K1]>
        (o: O, k1: K1, k2: K2): O[K1][K2] 
    //③
    <O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2]>
        (o: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3] 
}
let get: Get = (object: any, ...keys: string[]) => {
    let result = object
    keys.forEach(k => result = result[k])
    return result
}
//④
console.log(get(TestLog, 'options', 0, 'type')) //menGet 型(オーバーロードされた関数シグネチャを宣言)
Get 型の関数を作成するので type Get の宣言です。
関数呼び出し時にTest型のオブジェクト「TestLog」渡すので、その想定で説明していきます。
1つのキー、2つのキー、3つのキーでそれぞれ 呼び出す場合のための3つのケースを指定します。
type Get = {
    //①  ④で「TestLog」渡されるので「O」はTest型のオブジェ「TestLog」、
    //「keyof O」は「'id'|'name'|'options'」、
    //④で「'options'」が渡されるので O[K1] は、TestLog['options']になる。
    <O extends object, K1 extends keyof O>(o: O, k1: K1): O[K1] 
    //② O[K1] は TestLog['options'] の値、[{type: 'men', timestamp: 2020-06-23T16:14:36.967Z}]
    //なので O[K1] は配列になる。「keyof 配列」は「number」になるで「K2 extends number」になる。
    //④で「0」が渡されているので O[K1][K2] は TestLog['options'][0] になる。
    <O extends object, K1 extends keyof O, K2 extends keyof O[K1]>
        (o: O, k1: K1, k2: K2): O[K1][K2] 
    //③ O[K1][K2] は TestLog['options'][0] の値、{type: 'men', timestamp: 2020-06-23T16:14:36.967Z}
    //なので「keyof O[K1][K2]」は「'type'|'timestamp'」になる。
    //④で「'type'」が渡されているので O[K1][K2][K3] は TestLog['options'][0]['type'] になる。
    <O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2]>
        (o: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3] 
}②の箇所の 「「keyof 配列」は「number」になる」の説明。
※ この箇所はもっと的確な説明があるかと思います。
type a = []
type b = keyof a
let obj1:b = 0 //OK
let obj2:b = 100 //OK
let obj3:b = "" //エラー
let obj4:b = {} //エラー
let obj5:b = true //エラー関数/関数呼び出し
Get 型の関数を作成し、呼び出します。
let get: Get = (object: any, ...keys: string[]) => { //第1引数以後は可変長にしておく
    let result = object
    keys.forEach(k => result = result[k])
    return result
}
//④ 
console.log(get(TestLog, 'options', 0, 'type')) //men
//第2引数渡しまでのそれぞれの出力
console.log(get(TestLog, 'options')) //[{type: 'men', timestamp: 2020-06-23T19:04:04.449Z}]
console.log(get(TestLog, 'options', 0)) //{type: 'men', timestamp: 2020-06-23T19:04:04.449Z}まとめたもの
type Test = {
    id: number
    name: string
    options: {
        type: 'men' | 'women'
        timestamp: Date
    }[]
}
let TestLog: Test = {
    id: 10,
    name: 'tanaka',
    options: [{
        type: 'men',
        timestamp: new Date
    }]
}
type Get = {
    //①  ④で「TestLog」渡されるので「O」はTest型のオブジェ「TestLog」、
    //「keyof O」は「'id'|'name'|'options'」、
    //④で「'options'」が渡されるので O[K1] は、TestLog['options']になる。
    <O extends object, K1 extends keyof O>(o: O, k1: K1): O[K1] 
    //② O[K1] は TestLog['options'] の値、[{type: 'men', timestamp: 2020-06-23T16:14:36.967Z}]
    //なので O[K1] は配列になる。「keyo 配列」は「number」になるで「K2 extends number」になる。
    //④で「0」が渡されているので O[K1][K2] は TestLog['options'][0] になる。
    <O extends object, K1 extends keyof O, K2 extends keyof O[K1]>
        (o: O, k1: K1, k2: K2): O[K1][K2] 
    //③ O[K1][K2] は TestLog['options'][0] の値、{type: 'men', timestamp: 2020-06-23T16:14:36.967Z}
    //なので「keyof O[K1][K2]」は「'type'|'timestamp'」になる。
    //④で「'type'」が渡されているので O[K1][K2][K3] は TestLog['options'][0]['type'] になる。
    <O extends object, K1 extends keyof O, K2 extends keyof O[K1], K3 extends keyof O[K1][K2]>
        (o: O, k1: K1, k2: K2, k3: K3): O[K1][K2][K3] 
}
let get: Get = (object: any, ...keys: string[]) => { //第1引数以後は可変長にしておく
    let result = object
    keys.forEach(k => result = result[k])
    return result
}
//④ 
console.log(get(TestLog, 'options', 0, 'type')) //men
//第2引数渡しまでのそれぞれの出力
console.log(get(TestLog, 'options')) //[{type: 'men', timestamp: 2020-06-23T19:04:04.449Z}]
console.log(get(TestLog, 'options', 0)) //{type: 'men', timestamp: 2020-06-23T19:04:04.449Z}














 
  
  
  
  
  
  
  
 









 
 