constants(固定値)から型を作ったり使ったりするあれこれ
アプリケーション内で固有の文字列、配列、オブジェクト等がある場合はそれを型にして堅牢性を高めたい。
文字列
文字列の場合は単にどこかのファイルにexport constで宣言してそれを使いたいところでimportすればいい。
型もstringなので特に考えることはないはず。
配列
配列の場合、配列に含まれる要素のリテラル型とその配列に含まれるかという操作方法が必要になると思う。
型の作成
宣言した配列をas const
してその中身の型を作る。
const authors = ['ken', 'bob', 'alice'] as const
type AuthorsType = typeof authors[number] // "ken" | "bob" | "alice"
ちなみにas const
しない場合はAuthorsType
はstring
とみなされる。
as const
しない場合、配列の中身は書き換え可能なためリテラルとみなされない。(const
のためauthors
自体への直接代入はできないが配列の操作自体は可能)
ただし宣言時点で配列の中身はstring
しかないためstring[]
とみなされる。そのためtypeof
するとstring
になる。
const authors = ['ken', 'bob', 'alice']
type AuthorsType = typeof authors[number] // string
更についでに書くとas const
した時点でauthors
に対してpush
やpop
のような配列を変更する操作できなくなる(ビルドエラー)
固定値に含まれるかどうかのチェック
ユーザーの入力やURLなどからのパラメータが固定値のものと合致しているか調べるときのやり方。
単純に考えてincludes
すればいいだけと思うけどそうはいかない。
const authors = ['ken', 'bob', 'alice'] as const
const includeAuthors = (value: string):boolean => authors.includes(value) // Error
includes
の宣言を見てみると下記のようになっている。
interface ReadonlyArray<T> {
includes(searchElement: T, fromIndex?: number): boolean;
}
as const
したauthors
はReadonlyArray<"ken" | "bob" | "alice">
となるので当然第1引数もken" | "bob" | "alice"
なのでその固定値以外受け取れない。
もちろんas const
していなければ普通のstring[]
なのでstring
を引数にとってincludes
で判定できる。
なので判定にだけ使いたいとかならas const
なしで宣言すればいい。
const authors = ['ken', 'bob', 'alice']
const includeAuthors = (value: string):boolean => authors.includes(value)
型も作って判定にも使いたいような場合、as const
した配列をstring[]
とみなしてincludes
すればいい。
※もっといい感じのやり方があれば知りたい(as
を使うのでスマートじゃない気がする)
const authors = ['ken', 'bob', 'alice'] as const
type AuthorsType = typeof authors[number] // "ken" | "bob" | "alice"
const func1 = (value: string):boolean =>
(authors as unknown as string[]).includes(value)
const func2 = (value: string):boolean =>
authors.map(v => v as string).includes(value)
オブジェクトの配列から型を作る場合
オブジェクトの各フィールドごとの型を作ったりする方法。
const books = [
{ id: 1, name: 'タイトル1', author: '作者1', code: 1 },
{ id: 2, name: 'タイトル2', author: '作者2', code: 2 },
// ...
] as const
type AuthorType = typeof books[number]['author'] // "作者1" | "作者2"
配列内のオブジェクトがすべてauthor
フィールドを持って定義されていればエディタでauthor
は補完されるしなければauthor
を書いてもエラーになる。
検索などで必要なら先にmap
でauthor
だけの配列を作って使うこともできる。
(もともとの配列がas const
になっていなければAuthorType
はstring
になる)
const books = [
{ id: 1, name: 'タイトル1', author: '作者1', code: 1 },
{ id: 2, name: 'タイトル2', author: '作者2', code: 2 },
// ...
] as const
const authors = books.map(v => v.author)
type AuthorType = typeof authors[number] // "作者1" | "作者2"
オブジェクトから型を作る
const authors = {
KEN: 'ken',
ALICE: 'alice',
BOB: 'bob'
} as const
type AuthorKeyType = keyof typeof authors // "KEN" | "ALICE" | "BOB"
type AuthorValType = typeof authors[AuthorKeyType] // "ken" | "alice" | "bob"
// type AuthorValType = typeof authors[keyof typeof authors] // "ken" | "alice" | "bob"
オブジェクトのvalues
を肩にする
当たり前だけど型による制御と実際の値によるチェックは全然違うものなので、大元の固定値からそれぞれの機能を実現できるように作っていく、という考え方で良さそう。
Discussion