TypeScriptで一意なKeyを補完されるように管理したい
投稿日: 2024/06/25
更新日: 2024/07/01
フロントエンド開発をしていてWebStorage(LocalStorage, SessionStorage)やRecoil、React Query(TanStack Query)などに触れたとき、一意なkeyを管理する必要があります。
シンプルにKeyを管理する手法を考えましたので紹介します。
この記事でわかること
- 一意なKeyのシンプルな管理手法
- enumのようにKeyを追加するたびにほぼ同じ文字列を2回ずつ入力する必要はありません
- 型情報によって補完が効きます
- 重複Keyは型エラーを出します。
より多くのKeyを効率的に管理したい場合は階層的な管理を可能にしている以下の記事をおすすめします。
自分の今回の用途ではここまでこだわらなくてもいいかなと思って、もっと簡素なコードで良いというモチベーションで作りました。
本記事では階層的なKey管理はできませんが、とにかく短いコードで効率的なKey管理できることをゴールにしています。
僕のコードを紹介する前に、比較する管理手法としてEnumを使った方法を紹介します。
enumを用いた管理手法
以下のようにenumを用いる手法が引用先のブログで紹介されています。
非常にシンプルで補完も効きますが、KeyだけでなくValueも人間が毎回指定する必要があるのは少々面倒に感じてしまいます(こういう単調作業はミスにつながりやすいのでvalue側は指定しなくてもいいようにしたいところです)。
多少面倒ではありますが十分に実用的な手法です。
RecoilKeys.ts
export enum RecoilAtomKeys { TODO_STATE = 'todoState', NOTICE_STATE = 'noticeState' } export enum RecoilSelectorKeys { TODO_TODOS = 'Todo_todos', TODO_TODO_ITEM = 'Todo_todoItem', NOTICE_HAS_UNREAD_NOTICE = 'Notice_hasUnreadNotice' }
余談ですが、こちらの手法を使う場合はTypeScriptのenumを使わないほうがいい理由を、Tree-shakingの観点で紹介しますを一読してUnion Typesを使う実装に書き直すとより効率的になります。
連想配列のValueを自動で生成する手法
こちらが今回提案する手法です。
早速コードです。
frontend/helper/webStorage/localStorage.ts
const keysObject = { csrfToken: "", authToken: "", } as const type Key = keyof typeof keysObject const keys = Object.keys(keysObject) as Key[] export const localStorageKeys = keys.reduce( (obj, key) => ({ ...obj, [key]: key }), {} as { [key in Key]: string } )
このように作成した変数localStorageKeys
を用いると下の画像のように型情報から補完されて便利です。また、変数keysObject
にKeyを追加していく際に重複があれば型エラーが出るので事故を防止できます。
残念ポイントとしては重複を型エラーとして検知するために連想配列にしたことで、Valueに何かしらの値を入れておかないといけないことです(使用しないのに!)。
コードを順に解説していきます。
変数keysObject
に使用したい一意なKeyを入れた連想配列を代入しておきます。連想配列のValueは使用しないので空の文字列を入れています(null
やundefined
などなんでもいいです)。
type Key = keyof typeof keysObject
では変数keysObject
のKey一覧をUnion型として取得します。ここでは以下のようになります。
type Key = "csrfToken" | "authToken"
const keys = Object.keys(keysObject) as Key[]
では、実際に変数keysObject
からKey一覧を配列として取り出しています。keys === ['csrfToken', 'authToken']
となります。
(この時、型がstring[]
となってしまうのでasを用いてKey[]
型にしています。代入された変数keys
の型は("csrfToken" | "authToken")[]
になります。)
最後に以下のコードの処理についてです。
export const localStorageKeys = keys.reduce( (obj, key) => ({ ...obj, [key]: key }), {} as { [key in Key]: string } )
変数keys
を用いて、KeyとValueが同じ文字列の連想配列を生成します。
localStorageKeys === { "csrfToken": "csrfToken", "authToken": "authToken" }
となります。
まとめと余談
はじめはKey一覧を配列として管理するようにしていました。
以下のコードはおすすめしない
// このコードは非推奨 const keys = [ "csrfToken", "authToken", ] as const type Key = typeof keys[number] export const localStorageKeys = keys.reduce( (obj, key) => ({ ...obj, [key]: key }), {} as { [key in Key]: string } )
Keyを追加するときの記述が最小限で済むのですが、配列に重複した値があっても気づきにくいデメリットがありました。特にエラーも出ずに重複したKeyが勝手にまとめられるので、危険だと思います。
うまく型エラーを出せないか色々と健闘しましたが、結局いいアイディアは思いつきませんでした。もし、良いアイディアがあればコメントください。
実行時エラーなら簡単に出せますが、微妙ですよね(無いとまでは言わないですが)。
そういうわけで多少冗長になりますが、連想配列のほうを採用したのでした。
Noh を作ってるエンジニア。