Noh | エンジニア向け情報共有コミュニティ
Signup / Login

I want to manage unique keys being auto-completed in TypeScript

javascript
typescript

投稿日: 2024/06/25

更新日: 2024/07/01

This article is a translation of the following article.

https://noh.ink/articles/chptXdkJgVVo95InUAKr

While doing front-end development, we need to manage unique keys in WebStorage (LocalStorage, SessionStorage), Recoil and React Query (TanStack Query).

I came up with a simple method for managing keys, so let me introduce it.

What you will learn in this article

  • Simple management method for unique keys
    • You do not need to enter almost the same string twice every time you add a key like enums.
    • Auto-completion works based on type information.
    • Duplicate keys will result in a type error.

If you want to manage more Keys efficiently, we recommend the following articles that enable hierarchical management.

I made it with the motivation that it doesn't have to be so particular for my current use case and that a simpler code would be good. In this article, hierarchical key management is not possible, but the goal is to achieve efficient key management with a short code anyway.
I propose "Technique for automatically generating values for associative arrays".

Before introducing my code, I will introduce a method using Enum as a comparison management method.

Management method using 'enum'

The method using enums is introduced in the referenced blog as follows.

It is very simple and completes well, but it's a bit cumbersome that not only the key but also the value needs to be specified by a human every time (such monotonous work is prone to mistakes, so I would like the value side to be unnecessary to be specified).

It may be a bit cumbersome, but it is a sufficiently practical method.

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

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

This is a digression, but when using this method, it is more efficient to rewrite the implementation using Union Types after reading Reasons why it is better not to use TypeScript's enum from the perspective of Tree-shaking.

Technique for automatically generating values for associative arrays

This is the method we are proposing this time.

Here is the code.

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

By using the variable localStorageKeys created like this, completion from type information as shown in the image below is convenient. Also, when adding keys to the variable keysObject, if there are any duplicates, a type error will be displayed, preventing accidents.

vs codeで補完されてる例

One unfortunate point is that in order to detect duplicates as type errors, it was necessary to use an associative array, which requires some value to be set in the Value field (even if it is not used!).

I will explain the code step by step.

Store an associative array in the variable keysObject with unique Keys you want to use. Put an empty string as the Value in the associative array since it is not being used (null, undefined, or anything is fine).

The type Key = keyof typeof keysObject retrieves a list of keys for the variable keysObject as a Union type. Here, it looks like this:

type Key = "csrfToken" | "authToken"

With const keys = Object.keys(keysObject) as Key[], we are actually extracting the list of Keys from the variable keysObject as an array. keys === ['csrfToken', 'authToken'] will be the result.

(At this time, the type becomes string[], so we are using as to convert it to the Key[] type. The type of the assigned variable keys becomes ("csrfToken" | "authToken")[].)

Lastly, I would like to discuss the processing of the following code.

export const localStorageKeys = keys.reduce( (obj, key) => ({ ...obj, [key]: key }), {} as { [key in Key]: string } )

I will generate an associative array using the variable keys, where the Key and Value are the same string.
localStorageKeys === { "csrfToken": "csrfToken", "authToken": "authToken" }.

Summary and Side Notes

At first, I was managing the list of keys as an array.

以下のコードはおすすめしない

// このコードは非推奨 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 } )

When adding a Key, the description can be kept to a minimum, making it difficult to notice duplicated values in the array. Especially since duplicated Keys are automatically grouped together without any errors, I believe it is dangerous.

I tried various ways to generate a type error successfully, but in the end, I couldn't come up with a good idea. If you have any good ideas, please leave a comment.

If it's a runtime error, it can be easily displayed, but it's tricky (not to say it doesn't exist).

So, it may be a bit redundant, but we decided to adopt an associative array.

Table of Contents