🌝

TypeScript 書き始めのエッセンス

2020/09/28に公開

はじめに

こんにちは。

ここ最近は主に 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 の部分は、japanworldall の文字列のどれかに制限されます。

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 を使うことでより強力に型宣言ができます。

参考資料

Discussion