TypeScript

【TS】keyofを使ってオブジェクトと適切なキーを取る関数作成

2020/10/10

今回はTypeScriptがテーマです。

この記事では実例を交えて、以下について解説したいと思います。

  • ジェネリクスとextends
  • keyofの使い方

※TypeScriptの「ジェネリクス」自体の解説はしませんが、TS初心者向けの解説です。

前提:「オブジェクト」と「そのオブジェクトに含まれるキー」を引数に取る関数を作る

あるオブジェクトとそのキーを引数として受け取って、その値を返すだけのシンプルな関数があるとします。

function getValue(obj, key) {
  return obj[key]
}

const sample = {
  name: 'Taro',
  age: 10
}

getValue(sample, 'name') // 'Taro'

第2引数のキー名が間違ってたりすると、この関数は正しく動きません。 (ここでは一旦、値の検証とかは置いておくとします。)

なので、第1引数に渡されたオブジェクトに含まれるキーのみを、第2引数で受け取れるようにしたいと思います。

これをTypeScriptでは、

  • ジェネリクスとextends
  • keyof

上記の機能を使って、わりと簡単に書くことが可能です。

ジェネリクスとkeyofを組み合わせて関数の型を書く

先ほどの関数を、TypeScriptを使って書き直したのが以下になります。

function getValue<T, U extends keyof T>(obj: T, key: U): T[U] {
  return value[key];
}

const sample = {
  name: 'Taro',
  age: 10
}

getValue(sample, 'name') // 'Taro'
getValue(sample, 'hoge') // エラー

こうすることで、第2引数に渡される値は、第1引数に渡されたオブジェクトのキーのみとなります。

以下に、簡単にポイントを解説していきます。

extendsについて

ジェネリクス部分において、以下のように記述しています。

<T, U extends keyof T>

ジェネリクス部分でextendsを使うと、型パラメータに制約を設けることが可能となります。ここでは「型パラメータU」の型は、「keyof T」になる必要があると言っていることになります。

ちなみに「keyof T」のTは、第1引数の型パラメータTのことです。つまり、「keyof 第1引数に渡されたオブジェクト」ということですね。

では「keyof」の意味さえ分かれば、この関数の型は簡単に理解できそうです。

keyofについて

「keyof」というのはTypeScriptに用意されているキーワードで、オブジェクトの型に対して使うことが可能です。

「keyof」は、渡されたオブジェクトのキーから成るユニオン型を返します。ちょっと分かりにくいと思うので、以下のサンプルを見てみてください。

type TestType = keyof { name: string, age: number }; // TestTypeの型は  'name' | 'age' になる。

// もちろんinterfaceなどに対しても使える。
interface SampleObj {
  id: string;
  category: string;
}

type TestType2 = keyof SampleObj // TestType2の型は  'id' | 'category'

上記を踏まえて、以下の記述を再度見てみましょう。

function getValue<T, U extends keyof T>(obj: T, key: U): T[U] {
  return value[key];
}

const sample = {
  name: 'Taro',
  age: 10
}

// 第1引数にオブジェクト sample を渡している。
// そのため、第2引数の型は 'name' | 'age' というユニオン型になっている。
getValue(sample, 'name') // 'Taro'
getValue(sample, 'hoge') // エラー

「keyof T」と書くことで、「第1引数として渡されたオブジェクトT」の「キー名のユニオン型」が取得できています。

そしてextendsを併用しているので、 「型パラメータU」の型は「オブジェクトTのキー名から成るユニオン型」となるわけです。

まとめ

今回は「keyof」と、「keyofとジェネリクス」を組み合わせて使う方法について解説しました。

今回はシンプルな例でしたが、TypeScriptではもっと複雑な型を表現することができます。ノウハウがたまってきたら、今後もシェアしていきたいなと考えています。