🧪

WebStorageやRecoilの一意なKeyを補完されるように管理する

2023/01/08に公開

フロントエンド開発をしていてWebStorage(LocalStorage, SessionStorage)やRecoilなどに触れたとき、一意なkeyを管理する必要があります。

シンプルにKeyを管理する手法を紹介します。

この記事でわかること

  • 一意なKeyのシンプルな管理手法
    • enumのようにKeyを追加するたびにほぼ同じ文字列を2回ずつ入力する必要はありません
    • 型情報によって補完が効きます
    • 重複Keyは型エラーを出します。

より多くのKeyを効率的に管理したい場合は階層的な管理を可能にしている以下の記事をおすすめします。

https://zenn.dev/pione30/articles/3462befa564bc8

自分の今回の用途ではここまでこだわらなくてもいいかなと思って、もっと簡素なコードで良いというモチベーションで作りました。
本記事では階層的なKey管理はできませんが、とにかく短いコードで効率的なKey管理できることをゴールにしています。

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'
}

引用: https://engineering.linecorp.com/ja/blog/line-sec-frontend-using-recoil-to-get-a-safe-and-comfortable-state-management/

余談ですが、こちらの手法を使う場合は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は使用しないので空の文字列を入れています(nullundefinedなどなんでもいいです)。

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一覧を配列として管理するようにしていました。

Keyを追加するときの記述が最小限で済むのですが、配列に重複した値があっても気づきにくいデメリットがありました。特にエラーも出ずに重複したKeyが勝手にまとめられるので、かなり危険だと思います。

うまく型エラーを出せないか色々と健闘しましたが、結局いいアイディアは思いつきませんでした。もし、良いアイディアがあればコメントください。

実行時エラーなら簡単に出せますが、微妙ですよね(無しとまでは言わないですが)。

そういうわけで多少冗長になりますが、連想配列のほうを採用したのでした。

Discussion