TypeScript Tip #5

Apr 10, 2022

3 mins read

背景

今回もmattさんの動画をまとめていく
TypeScript Tip #5


概要

型推論された型に対してextendsを使用するまとめていく。


本題

実際のサンプルコードは以下。 getDeepValue関数の引数にオブジェクトを渡して、そこから型推論をさせている。
第2引数、第3引数のオブジェクトのキーを指定して、最終的なバリューを取得できるような関数を作成する。

export const getDeepValue = <
      Obj, 
      FirstKey extends keyof Obj, 
      SecondKey extends keyof Obj[FirstKey]
  >(
      obj: Obj,
      firstKey: FirstKey,
      secondKey: SecondKey
    ): Obj[FirstKey][SecondKey] => {
        return  obj[firstKey][secondKey]
}

const obj = {
  foo: {
    a: true,
    b: 2
  },
  bar: {
    c: 'cool',
    d: 2
  }
}

console.log(getDeepValue(obj, 'bar', 'd'))
  1. 引数の第2引数FirstKeyは型推論されたObjに制約を設けている。
FirstKey extends keyof {
    foo: {
        a: boolean;
        b: number;
    };
    bar: {
        c: string;
        d: number;
    };
}

🔽

FirstKey extends "foo" | "bar"

よって、FirstKeyはfoobarのどちらかになる。

  1. 引数の第3引数SecondKeyは型推論されたObjと上記のFirstKeyのルックアップ型で制約を設けている。
    例えば、第2引数がfooの場合、以下の様になる。
<
      Obj, 
      FirstKey extends 'foo', 
      SecondKey extends keyof Obj['foo']
>
    
🔽

<
    Obj,
    FirstKey extends 'foo',
    SecondKey extends "a" | "b"
>

上記の様に型推論され、第2引数の値によって第3引数の値も変更されるため、より安全に型定義できる。

よって、以下の様なパターンは型エラーとなる。

console.log(getDeepValue(obj, 'foo', 'd'))
// Argument of type '"d"' is not assignable to parameter of type '"a" | "b"'.

console.log(getDeepValue(obj, 'bar', 'a'))
// Argument of type '"a"' is not assignable to parameter of type '"c" | "d"'

まとめ

今回は短めだったが、型推論は強力のため、うまく使用すれば汎用的な型定義ができそうだなと感じた。
意外とkeyofを使用した時に、リテラル型のユニオンが生成されることが抜けていたので勉強になった。


参考文献

TypeScript Tip #5