この記事について
TypeScriptで開発をする中で忘れたくない内容を個人的にメモしました。
Object.keysの戻り値を文字リテラルユニオンの型にする
文字リテラルユニオンの型とは("apple"|"orange"|"banana")[]
のような形式で、appleかorangeかbananaのいずれかの文字リテラルを値にもつ配列の型のこと。
Object.keys()
の戻り値の型はstring[]
になる。これはオブジェクトのキーに指定できる型がstring型とnumber型の両方あることが原因。
これで生じる不都合としてはkeyの値をもとにしたSwitch文による条件分岐とか、下の例のような、元のオブジェクトとkeyから値を取得しようとしたりすると、エラーになること。
const testObj = {
name: "shun",
food: "ramen",
hobby: "volleyball",
};
// testObeKeys:string[]
const testObeKeys = Object.keys(testObj);
// エラー:型 'string' の式を使用して型 '{ name: string; food: string; hobby: string; }' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
// 平たくいうと"testObj"の値をキー指定で取得する時は、文字リテラルにしてね。というエラー(string型だと、キーに無い値も指定できてしまうから)
const rtn = testObeKeys.map((key) => {
return {
key: `NEW_${testObj[key]}`,
};
});
上の例の場合、Object.entry
を使えば解決するけど、今回はObject.keys
の戻り値の型をstring[]型から文字リテラルユニオン配列型に変える
// 引数のオブジェクトのキーを文字リテラルユニオンの配列型で返す関数
const getKeys = <T extends { [key: string]: unknown }>(obj: T): (keyof T)[] => {
return Object.keys(obj);
};
const keys = getKeys(testObj);
const rtn = keys.map((key) => {
return {
key: `NEW_${testObj[key]}`,
};
});
分解して解説します。
まず大前提として、文字リテラルユニオンの配列型にするのだから、そもそもキーにnumber型が存在するオブジェクトは受け付けないようにする。
それをやっているのがここ。
// extendsを使い、キーがstring型のobjのみ引数として受け付けるようにする。
<T extends { [key: string]: unknown }>(obj: T):
次にgetKeys
関数の戻り値の型(keyof T)[]
の部分。
// keyof Tの戻り値は"name" | "food" | "hobby"となる。それを()で閉じて、[]を付けると、
// ("name" | "food" | "hobby")[]となる。
(keyof T)[]
Object.keys(obj);
は通常string[]型を返すけど、関数の戻り値の型として(keyof T)[]
を指定することで、string[]型が(keyof T)[]型にキャストされる。
(厳密には、関数の戻り値の型を(keyof T)[]型に指定しているので、TypeScriptが(keyof T)[]型と型推論してくれる)
め