プログラミングTypeScriptを読んだ(2章・3章)
最近フロントエンド開発に携わることが多いので、Reactを触ってたのですが、 JavaScriptで書いていたところ、やはり実行時エラーに悩まされることが多く、TypeScriptに移行してみようと思っておりました。
ちょうどいいタイミングで、オライリーさんからプログラミングTypeScript ―スケールするJavaScriptアプリケーション開発が出版されたので、 コロナで自宅にいる時間が増えたのもあり、読んで自分なりに学んだことをこまとめておこうと思います。
2章(TypeScript:全体像)
- Typescriptのコンパイラはts→jsに変換する際に型チェッカーを動作させて、型安全性の検証を行う
- jsになったあとには型チェックなどは行わない(ここはもうjsの範囲でランタイムが行うことにはtsは影響しない)
- あくまでtsが行うのは、jsでは全て実行時エラーになっていたもののいくつかをコンパイルエラーにすること
- tsconfigファイルによって、tsファイルのコンパイル の設定やコンパイル 範囲を指定することができる
- tslintファイルによってコーディングスタイルを矯正する
3章(型について)
any型
unknown型
- any型と同様に任意の型をとることができる
- 変数は、コード内でどの型であるかチェックされるまで利用(演算処理など、比較は可能)することができず、エラーが出力される
- TypeScriptがある変数をunknown型と推論することはない
boolean型
number型
bigint型
- number型の扱える253よりも大きな整数を扱うことができる
- JavaScriptエンジンごとにサポート状況が異なるため、利用は注意が必要
symbol型
- 必ず一意になるように定義され、その性質を利用してマップやオブジェクトの文字列キーの代わりに利用される
- リテラル型と推論させるには、unique symbolとアノテートする必要がある
object型
- object型であることにあまり意味がなく、その中の構造において値の型を推論もしくは定義されていることを好む(=structural typing、というらしい)
- constでobjectを定義したとしても、中の変数はリテラルで推論されない(JavaScriptのobjectが変更可能であるため)
- objectのプロパティは厳格にチェックされる(プロパティの余剰も省略も許可しない)
- ただし、省略可能(?修飾子)や余分なプロパティがあること([key: T]: U)を明示的に宣言することもできる
- 他の修飾子はreadonly修飾子があり、読み取り専用と定義できる
- 空のobject型({}で表現されるもの)の挙動は全ての型が割り当て可能なため、できるだけ避けること
- Object型(≠object型)もObjectプロトタイプの型に割り当て可能なため避けるべき
配列
- 配列型は型を統一すべきだが、複数の型からなる配列も宣言可能
let a: (string|number)[] = [] // string 型とnumber型を代入できる配列として配列aを宣言 let a: Array<string|number> = [] // これも同じ意味
- 配列の宣言方法は上記2パターンがあるが、どちらもパフォーマンスや挙動に影響がないので、好みで使い分ければ良い
タプル
- 配列のサブタイプで、固定長の配列を宣言するためのもの
- 要素は?修飾子を利用して、省略可能な要素として宣言可能
- 要素はスプレッド構文(...)を利用して、可変長変数としても宣言可能
列挙型
- 宣言時は先頭大文字の単数形が慣習(languageではなく、Language)、列挙型の変数名も、要素名も同様
- 文字列列挙(文字列がキーで、値が文字列)と、数字列挙(文字列がキーで、値が数値)がある
- 数値の値は推測もされるが、明示的に値を入力することも可能
- 一つの列挙型変数の要素の中で、文字列と数値を値に混ぜることもできる
- キーからだけでなく、値からの参照も可能だが、とても危険であり、存在しない値からの参照もコンパイル自体は通る!(ただし、実行時にはもちろんundefinedになる)
- 値からの参照(=逆引き参照)を制限するには、constを用いて宣言すること。これによって危険なアクセスを抑止できる
- enumは明示的に要素の方を宣言しないかぎり、自由度を持ってしまうため、できるだけ利用するのを避ける、一時的なローカルでの利用のみを心がける方が良い
null, undefined, void, never
- nullは値自体が欠如していることを示す
- undefinedは値がまだ定義されていないことを示す
- voidは戻り値の型であり、明示的に何も返さないことを示す
- neverは決して戻ることのない関数の型
その他・Tips的なもの
- 型アノテーションを行うことで、型制約が強制されるので、基本的には型アノテーションを利用する
- index signature:以下の形式で表現される、オブジェクトが多くのキーを含む可能性をTypeScriptへ伝える方法
- Object型や{}でobject型を宣言するのはNG。なぜなら以下のようにコンパイラに解釈されるから
let a: {} = 3 // コンパイラは許容する。JavaScript 的には var a = 3 として解釈される let b: Object = 3 // 上記と同じ // object型として宣言しようした場合に、リテラルを許容してしまう可能性があるため、適切ではない // しかし型の名前の付け方もう少しどうにかならなかったのかなぁ。。とも思う
- 型エイリアスを使うことで、定義する変数が理解しやすくなる
type Age = number // number型をAge型としてエイリアス let age: Age = 30 // 変数ageをAge型(=number型)として定義し、55で初期化している
- ただし、この辺りの命名規則は適切に運用しないと混乱を招きそう
- 型自体を演算できる、「合併(|)」と「交差(&)」が存在する
- 合併の場合は、いずれかのプロパティを持つことができ、交差の場合は、すべてのプロパティを持つ必要がある
- 型推論は、基本的に「疑わしきは罰せず」がルール。怪しいものはその上位概念と推定する
長くなったので、4章以降は別な記事にまとめます。