はじめに
定数オブジェクトのvalue
からkey
名を取得したい場面があって、定数オブジェクトに対して汎用的に利用できる関数を作成してみた。
こんな書き方もありそう、ここあんまり良くない実装になっているなどご指摘等々あれば気軽にコメントいただけると嬉しいです。
作成した関数
const getKeyByValue = <
OBJECT extends Record<string | number, any>,
KEY extends keyof OBJECT,
VALUE extends OBJECT[KEY]
>(
obj: OBJECT,
val: VALUE
): KEY => {
// key と value を逆にしたオブジェクトを生成
const keyValueReversedObj: Record<VALUE, KEY> = Object.fromEntries(
Object.entries(obj).map(([key, value]) => [value, key])
);
// そのオブジェクトから key(第2引数の val)に応じた値を取得
return keyValueReversedObj[val];
};
ジェネリクスを利用していてかなり読みづらくなってしまっているものの、やっていることとしては
-
key
とvalue
を逆にしたオブジェクトを生成 - そのオブジェクトから
key(第2引数のval)
に応じた値を取得
しているだけである。
解説
こんなユーザーの状態を表す定数をサンプルとして考えてみる。
const UserStatusValue = {
non: 0,
init:1,
active: 2,
} as const;
// 出力結果はこうなる
const key = getKeyByValue(UserStatusValue, 2);
console.log(key); // => active
Genericsの部分
// 第一引数に取れる obj は { key: value, ... } の形式のオブジェクトに限定する
OBJECT extends Record<string | number, any>,
// 返り値は OBJECT の key に限定する
// UserStatusValue の場合 "non" | "init" | "active" となる
KEY extends keyof OBJECT,
// 第二引数に取れる val は第一引数の value に限定する
// UserStatusValue の場合 0 | 1 | 2 となる
VALUE extends OBJECT[KEY]
第一引数をセットした時点で、第二引数に取れる値が限定されるためUserStatusValueに存在しないvalueを第二引数に指定するとエラーになる
// Argument of type '3' is not assignable to parameter of type '0 | 1 | 2'.
const key = getKeyByValue(UserStatusValue, 3);
getKeyByValue() を利用しない場合はこんな形になりそう
getKeyByValue()
を利用しない場合、各定数オブジェクトに対してその都度、こんなswitch
関数を作成していく必要があると思う。
// こんな関数を各オブジェクトごとに書く必要がある
export const getKeyUserStatus = (
statusVal: typeof UserStatusValue[keyof typeof UserStatusValue]
) => {
switch (statusVal) {
case UserStatusValue.non:
return "non";
case UserStatusValue.init:
return "init";
case UserStatusValue.active:
return "active";
}
};
const key = getKeyUserStatus(2);
console.log(key); // => active
getKeyByValue()
を作成することで、各定数オブジェクトに対してその都度、こんなswitch
関数を作成していく必要がなくなる。
この関数の微妙なところ
const assertion されてないオブジェクトの場合、存在しない value を第二引数に指定できてしまう
as const
されてないオブジェクトの場合、存在しないvalue
も第二引数に指定可能になってしまう。その場合undefined
が返ってしまう。
export const UserStatusValue = {
non: 0,
init: 1,
active: 2,
};
const key = getKeyByValue(UserStatusValue, 3); // エラーにならない
console.log(key); // => undefined
なので、as const
を使った定数を前提とすることの認識を統一する必要などがありそう。
Generics が読みづらい
単純に読みづらい、かつ、ここまでGenerics
を頑張って使う必要があるかも微妙な感じがする。
最後に
試行錯誤しながら、自分なりに書きました。
冒頭にも述べたように、もっと良いやり方、懸念点、指摘事項などあればコメントいただけると大変嬉しいです。
ありがとうございました。