JY-CONTENTS

JY

JY-CONTENTS
search

+

MENU

【Typescript】keyof 演算子

【Typescript】keyof 演算子

(DATE)

-

2020.06.23

(CATEGORY)

-

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')) //men

Get 型(オーバーロードされた関数シグネチャを宣言)

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}

NEW TOPICS

/ ニュー & アップデート

SEE ALSO

/ 似た記事を見る

JY CONTENTS UNIQUE BLOG

search-menu search-menu