TypeScript Tip #1

Apr 7, 2022

6 mins read

背景

前回のmattさんのTypeScript Tip #15が勉強になったので、最初から#1からまとめていく

概要

export const fruitCounts = {
    apple: 1,
    pear: 4,
    banana: 26
}

上記の様なコードがあった時に、特定のキーを取得する型エイリアスで定義すると以下になる。

type SingleFruitCount = 
    | {
        apple: number    
    }
    | {
        banana: number
    }
    | {
        pear: number
    }

const singleFruitCount: SingleFruitCount = {
    apple: 10,
}

SingleFruitCountの型エイリアスを作成すれば、型定義できるが定数fruitCountsを型推論させて型エイリアスを作成する。

本題

最終的なコードは以下になる。具体的に解説していく。

type FruitCounts = typeof fruitCounts

type NewSingleFruitCount = {
    [ K in keyof FruitCounts ]: { 
        [ K2 in K ]: number
    }
}[keyof FruitCounts]

//  type NewSingleFruitCount = {
//      apple: number;
//    } | {
//      pear: number;
//    } | {
//      banana: number;
//  }
  1. typeof fruitCountsは型推論が効いて以下の型が作成される。
type FruitCounts = {
    apple: number;
    pear: number;
    banana: number;
}
  1. [ K in keyof FruitCounts ]keyof FruitCountsは型エイリアスのオブジェクトのキーのみを取得することができる。
    よって、keyof FruitCounts"apple" | "pear" | "banana"変換される。
    そうすると、[ K in keyof FruitCounts ][ K in "apple" | "pear" | "banana" ]に変換し、マップ型で型を繰り返し実装される。
    参考Notion: Map型
  2. [K2 in K]: numberKは単独のリテラル型( "apple" | "pear" | "banana)になる。なので、K2Kと同じリテラル型になる。
type NewSingleFruitCount = {
    apple: {
        apple: number;
    };
    pear: {
        pear: number;
    };
    banana: {
        banana: number;
    };
}

なぜ新たに[K2 in K]マップ型を作成しているかというと、もしK: numberで実装した場合、

type NewSingleFruitCount = {
    apple: {
        K: number;
    };
    pear: {
        K: number;
    };
    banana: {
        K: number;
    };
}

Kのリテラル型になり、マップした変数Kが使用できなくなる。オブジェクトのキーで変数を使用したい場合は、インデックスシグネチャを使用するしかなくなる。

  1. [keyof FruitCounts]["apple" | "pear" | "banana"]になり、ルックアップ型でキーを指定して取得することできる。ルックアップ型がユニオン型になっているため、取得した型もユニオン型で生成することができる。
    これまでのコードを型推論したコードを記載すると
type NewSingleFruitCount = {
    apple: {
        apple: number;
    }
    pear: {
        pear: number;
    }
    banana: {
        banana: number;
    }
}["apple" | "pear" | "banana"]

になり、以下のコードになる

type NewSingleFruitCount = {
    apple: number;
} | {
    pear: number;
} | {
    banana: number;
}

まとめ

TypeScriptにはユーティリティ型が充実しているため、マップ型やルックアップ型を使うことが少ないが、いつでも使える状態にしておきたい。

参考文献

TypeScript tip
Map型