TypeScript 書き始めのエッセンス
はじめに
こんにちは。
ここ最近は主に Web 開発の場面において、TypeScript の導入がデファクトスタンダードになってきている気がします。
JavaScript から TypeScript へ移行しているプロジェクト、初めてのプログラミング言語が TypeScript である人、その利用シーンは多岐に渡ると思います。
そのような TypeScript を書き始めて間もない環境の中で、単純に型定義を定義するだけではなく、ほんのちょっと一歩踏み込んだことを自分の思考の整理も兼ねて、より TypeScript を便利に使えるエッセンスを書いてみたいと思います。
エッセンス
as const (v3.4)
as const
const アサーションを使用すると、型の拡充を抑えることができます。
例えばこのような記述です。
const book = {
id: '12345',
title: '坊っちゃん'
} as const;
as const
アサーションを使うと、それぞれのプロパティが readonly にできます。
推論された型
const book: {
readonly id: "12345";
readonly title: "坊っちゃん";
}
仮に、このプロパティに値を代入しようとすると、エラーが発生します。
obj.title = '吾輩はねこである';
// (property) title: "坊っちゃん"
// Cannot assign to 'title' because it is a read-only property.ts(2540)
ジェネリック型
例えば、ある関数に渡すオブジェクトの形状がわからない場合、ジェネリック型を使うと便利です。これは TypeScript に限らず Java などの他の言語にもサポートされている機能です。
例えば、渡されたオブジェクトが number かどうかを判定する関数があったとします。
const isNumber = <T>(arg: T): boolean => {
return !!(typeof arg === 'number');
};
つまり、なんのオブジェクトが渡ってくるかわからないけど、とりあえず受け取って、それを typeof で判定して、そのオブジェクトが number かどうかの結果 boolean を返すよといっています。
実際にこの関数を実行すると、
console.log(isNumber('2')); // false
console.log(isNumber(2)); // true
console.log(isNumber({})); // false
console.log(isNumber(false)); // false
となり、引数 arg がどの型でも許可されその結果を受け取ることができます。<T>
はジェネリック型パラメータを宣言する方法で、実際に使用するときに型が確定します。
ジェネリック型を使えそうな場合は使うと、コードを再利用可能でかつ汎用的に保つことができそうです。
Mapped Type (v2.1)
Mapped Type はより安全に型を宣言できる強力な機能です。
例えば下記のコードです。
const BOOK_TYPE
の型定義が { [P in Key]: BookType }
として定義されていると思います。これが何を示しているのかというと、[P in Key]
は key の部分を表しますが、in Key
となっているように、type Key = …
で宣言されている key を指定する必要があります。
つまり、下記の例だと、BOOK_TYPE オブジェクトの key は、'JAPAN'
か 'WORLD'
か 'ALL'
のみ指定可能ということになります。
そして、その key に対する value として、BookType
が指定されているので、value の部分は、japan
か world
か all
の文字列のどれかに制限されます。
type Key = 'JAPAN' | 'WORLD' | 'ALL';
type BookType = 'japan' | 'world' | 'all';
export const BOOK_TYPE: { [P in Key]: BookType } = {
JAPAN: 'japan',
WORLD: 'world',
ALL: 'all',
} as const;
仮に、type Key で指定されていない 'OTHER' を BOOK_TYPE で定義しようとすると、
export const BOOK_TYPE: { [P in Key]: BookType } = {
JAPAN: 'japan',
WORLD: 'world',
ALL: 'all',
OTHER: 'other'
} as const;
Type '{ readonly JAPAN: "japan"; readonly WORLD: "world"; readonly ALL: "all"; readonly OTHER: "other"; }' is not assignable to type '{ JAPAN: BookType; WORLD: BookType; ALL: BookType; }'.
Object literal may only specify known properties, and 'OTHER' does not exist in type '{ JAPAN: BookType; WORLD: BookType; ALL: BookType; }'.ts(2322)
のようになります。
まとめ
- const アサーションで readonly にすることができます。
- どのような型が渡ってくるかわからない場合は、ジェネリック型を使うことで汎用性が高まります。
- Mapped Type を使うことでより強力に型宣言ができます。
参考資料
- O'Reilly プログラミング TypeScript Boris Cherny (2020)
- https://qiita.com/daishi/items/a413f8e2141cb7c7eec5
- https://qiita.com/kobanyan/items/2fdd6b0ed7411ff9980e
Discussion