🐶

TypescirptのRecord<T, K>について理解する

2023/06/26に公開

背景

typescriptでRecord<T, K>を使う場面に遭遇したので備忘録として記録しておく。

状況

あるオブジェクトのキーを特定の文字列に変換する関数translateを作成していました。

translate
const target = {
  name: "hoge",
  age: 20,
}

const translateText = {
  name: "名前",
  age: "年齢",
}

const result = translate(target, translateText)

// result = {
//   名前: "hoge",
//   年齢: 20,
// }

translate関数を型安全にするために、以下のように型付けしました。

translate
const translate = async <T extends Object>(
  target: T,
  translateText: {[key: keyof T]: string}
) => {};

targetをT型のオブジェクトに制限しつつ、translateTextはtargetのkeyをkeyに持つオブジェクトに制限したかったのです。

しかし、コンパイラに以下のように怒られました。

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.ts(1337)
(parameter) key: keyof T

typescriptではインデックスシグネチャはstring型かnumber型しか許されていないのに、リテラル型を渡しているのが原因みたいです。

さてどうしたものかと色々調べているとRecord<T, K>という組み込みの型があること知りました。

Record<T, K>について

Record<T, K>の型定義を見てみると以下のようになっています。

lib.es5.d.ts
/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

つまり、あるオブジェクトのキーがKで、それぞれのキーに対応する値がTの型を持つことを保証します。
今回の例で言うとtargetのキーである、nameとageの型を任意の型Tであると保証できるのです。
まさに今回のような場合にはピッタリです。

結論

最終的には以下のコードになりました。

translate
const translate = async <T extends Object>(
  target: T,
  translateText: Record<keyof T, string>
) => {};

これで

targetをT型のオブジェクトに制限しつつ、translateTextはtargetのkeyをkeyに持つオブジェクトに制>限したかった
と言う問題を解決できました。

めでたしめでたし。

Discussion