(静的な)型が…使える!
JavaScriptへの注目と共にかのMicrosoft発で2012年10月に発進、2021年4月現在はv4.2.4まで進化したAltJS言語の代表格、TypeScript。2017年にGoogleでも6番目の社内標準言語に採用、フロントエンドフレームワークではAngularJSのv2のAngularが最初から言語にTSを採用、ReactがTypeScriptと親和性が高いのは前から有名、遅れていたVue.jsも2020年秋のv3から対応を強化しました。
クラウドではコードでAWSインフラを定義するAWS CDKの技術でも、使える言語群に最初からTypeScriptが用意されています。日本でも最近のフロント界隈では規模のある新規開発でJSとTSの2択だったらもう完全上位互換のTSを……という流れになっていますね。
前から読もうと思っていたので、コロナの中久々に都内に行った時にオライリーのカレンダーと一緒に物理本をGetしていました。原著は2019年、日本語訳は2020年3月発行、著者のBoris Chernyさんは現Facebookのエンジニア。現時点で日本で手に入る最新の、がっつり370ページに及ぶ本格的なTypeScript本です。
- (静的な)型が…使える!
- 1章 イントロダクション
- 2章 TypeScript:全体像
- 3章 型について
- 4章 関数
- 5章 クラスとインターフェース
- 6章 高度な型
- 7章 エラー処理
- 8章 非同期プログラミングと並行、並列処理
- 9章 フロントエンドとバックエンドのフレームワーク
- 10章 名前空間とモジュール
- 11章 JavaScriptとの相互運用
- 12章 TypeScriptのビルドと実行
- 13章 終わりに
- まとめ:満を持して登場の、日本語で読める最新のTSの書
1章 イントロダクション
型安全(type safety)
がTSの特徴。型を使てプログラムが不正なことをしないよう防ぐことを、事前のコンパイルレベルの段階で教えてくれるのがTSの特徴だよ……という話を軽妙な語り口調で述べながら、型を求める旅が始まります。
2章 TypeScript:全体像
2.1 コンパイラー
TSコード→抽象構文木(AST)
へ変換されたところで型チェッカー(typechecker)
が動いて不正を全てチェック。そのあとJSコードに変換され、ブラウザ内でJSコードが抽象構文木に変換→バイトコードに変換→ランタイムが評価して実行。の流れ。
CやJavaやC#は後半のコンパイルの過程で型チェックが入るのでここが違う。
本書の語り口だと、読者は通りの向かいの店のバリスタの女の子に一目惚れしている設定になっているらしく、こういうところが面白いです。
2.2 型システム (type system)
プログラムに型を当てはめるために型チェッカーが使用するルールの集まり。書く人が明示的な構文を使って型をコンパイラーに伝える方法、型を自動的に推論する方法の2つ。TSは両方から発想を得ている。
JSでは型は動的にバインドされエラーが分かるのはほとんど実行時だが、TSでは静的バインドであり、コンパイル時に間違いが分かる。漸進的型付け言語(gradually typed language)
なので必ずしもすべての型が分かっていなくても動いてくれる。
細かいギャグではJavaScriptの魔女バブモーダなる人が出てきます。(ゲームにもなった1980年代のファンタジー映画『ウィロー』に出てくる悪の女王)
2.3 コードエディターのセットアップ
一応他のエディターも上げられてはいますが、作者さんもお気に入りは素晴らしい編集体験をもたらしてくれるVSCodeと書いてあります。まあVSCode一択ですよね。
言語のオプションはルート直下にあるtsconfigf.json
で設定。以前はTSLint
という静的解析ツールもあったが現在はESLint
統一。
3章 型について
型(type)
とは、型とそれを使ってできる事柄の集まり。できる事柄というのは ||
とか &&
とか +
-
とかの演算や操作、メソッド呼び出しも含む。ということでTSのコアである型を述べていく章。
3.1 型についての議論
function squareOf(n: number)
で引数n
は数値型という制約を付けるのが、型アノテーション(type annotaion)
。注釈をつけるという意味で他の言語でもよく使いますが、TSでは型を指定するために使用。
3.2 型の初歩
any
は事実上使っちゃダメといのはまあそうでしょうね。コンパイラでエラーとするよう設定もできるとのこと。unknown
型はTSが決して推論することはない型で、特定の型と分かってから初めて演算したりできる。let e:true = true;
のようにboolean
型の中のtrue
しかとりません、とより狭く宣言するのが「リテラル型」(literal type)
。このへんが不思議な感じがします。Java使いに自慢できると書いてある……!let f: 12.345 = 12.345;
のように特定の数値もリテラル型で宣言できるんですね。ここも初見だと不思議な感じ。- あるオブジェクトが中に特定のプロパティを持つかだけを重視して名前は気にしないのが「構造的型付け
(structual typing)
」=ダックタイピング。JSに習ってTSもこの方針。
let c: {firstNm: string, lastNm: string}
のようにオブジェクトの形状を宣言できる!素晴らしい! - キーに対応する値が
undefined
でも許すときは、c?: string
のように?
をつける。 [keyName: T]:U
で、T型の任意のキー名に対するU型の値がたくさんあることを表現。T型は数値か文字列のみ。- オブジェクトの宣言には形状が分かっていれば
{key: string}
、もしくはobject
型。空の{}
、Object
型は避ける。
こういうことをPureなJavaScriptでやっていた時は関数コメントにオブジェクトの中身を明記したりキー名を分かりやすくしたりでなんとかしていたのですが、おおお……さすが2010年代の言語だ……となります。
type Age = number
のようにして型エイリアスが可能。これも視覚的に分かりやすそう。- 型を
|
で繋げて合併型(=共用体型、共用型、union型)、&
で繋げて交差型。 - 配列の宣言は
Array<string>
とstring[]
両方あるが後者が多い模様。配列の中身の型も推論してくれる! - 配列の0番目はnumber固定、1番目はstring固定...などを定義できるのがタプル。
readonly
もつけられる。これはオブジェクトで示した方が綺麗であまり使わないような気がします。 null
==> 値がないこと
undefined
==> 値がまだ割り当てられていない変数
関数の戻り値指定のvoid
==> return文がなく戻り値がない。(あるいは「何もない」が戻る)。ここまではJSと同じ。
そしてnever
==> 決して戻ることのない関数の戻り値を表す型。必ずErrorが投げられる関数や無限ループの関数など。- 列挙型
(Enum)
でJan, Feb, Mar...など文字列を定義すると実際の値には数値が自動で割当たる。White = '#FFFFFF', Black = '#000000'
のように文字列=文字列でも指定可能でこちらのみが推奨。
数値を使うのは落とし穴があり、本書ではenum自体の使用を控えることを推奨していました。けっこう便利に見えるのですがどうなのでしょう……? やはり定数を持った静的クラスで定義なのでしょうか。
4章 関数
4.1 関数の宣言と呼び出し
- 引数は正確には「パラメーター」。
useId?: string
のように?をつけると省略可能。PHPなどだとこれはnull扱いですがTSではundefined
を表す。 useId = 'anonymous'
のようにデフォルト引数可能。func(...numbers: number[])
のようにして可変長の配列も受け取れるレストパラメーター(rest parameter)
がある。これはTSオリジナルの機能ではなくES2015の機能。JavaScriptで非推奨になった悪名高いarguments
は不要で安全! なおRESTful API のレストとは違うので注意。call, apply, bind
も使える。この辺自分は使わないので復習します……- 悪名高い(?)
this.
ですが、関数の引数で(this: Date)
と何を指すかを指定できる。ありがたい……! - ジェネレーターとイテレーターはJSでも自分はあまり使わないので復習してきます……
関数の型は
Function
型ではなく、(a: number, b: string) => string
のように引数と戻り値を含めた独自の型になる。「呼び出しシグネチャ」と呼ぶ。このへんVSCodeで実際に弄ると分かってきます。記法に=>
があるけど関数本体のアロー関数と違うので注意。予め関数の引数と戻り値を
type FuncName
のように呼び出しシグネチャで定義していれば、関数の実体を記述する時にアノテーションで型は明示しなくても推論してくれる。「文脈的型付け」という。(contextual typing)
- 呼び出しシグネチャで
type Reserve = (from: Date, to: Date, dest: string) => ReserveObj
と書いているのは省略形。正式版は{}をつけて
type Reserve = { (from: Date, to: Date, dest: string): ReserveObj; (from: Date, dest: string): ReserveObj; }
のように複数指定できる。なんと関数のオーバーロードが可能!(Java世代的に感動しますw)
- でもJSは引数をチェックしないので、関数の本体で
instanceOf
やtypeof
でチェックしないといけない。
全体的にJavaScriptもほんとはこう動いてほしいんだけどな……とか、なんかJSのこれって地雷が埋まってそうだから避けよう、としてきたところがTypeScriptだと言語仕様でうまく吸収され、安全に使えるようになっている印象です。
4.2 ポリモーフィズム
- 型レベルの制約を強制するために使われるプレースホルダーを「ジェネリック型パラメーター」(あるいは多層型パラメータ―)と呼ぶ。
- 下のように定義する。第1引数がT型の配列。第2引数がフィルタする関数で引数がT型、戻り値が残るか弾かれるかのboolean。外側の関数の戻り値がフィルタされたT型の配列、という意味。
type Filter = { <T>(array T[], f: (item: T) => boolean): T[]; }
- 型を表す変数名はオブジェクト指向よりなら慣例的に
T, U, V...
を使う。 - これによって型シグネチャで概略を記述、その後で値を埋め込んでいくプログラミングスタイル「型駆動開発
(type-driven development)
」が可能になる。シグネチャを見れば大体関数の中身が想像がつくようになる。
最終的にはJavaScriptになって動く訳で独特なところもあるのですが、Javaにおける ArrayList<String>
みたいなことも普通に書くことができ、おおお……TypeScriptすごいな!となりました。
5章 クラスとインターフェース
最初はチェスのクラスを例にとり、TypeScriptの大きな特徴であるクラス周りを解説しています。
- 他のオブジェクト指向言語と同様にクラス宣言ができる!
extends
で普通に継承できる。 - クラスのプロパティ(=メンバ変数)、メソッドにはアクセス修飾子で
public, protected, private
abstract class { ...
と宣言すると抽象クラスになりインスタンス生成不可。しかしメソッドは定義できる。- 親クラスのメソッド呼び出しは
super.parentMethod()
、コンストラクタ呼び出しはsuper()
。 - メソッドの戻り値に
this
を使うと自身のインスタンスを表せる。
親クラスを呼び出すのはC#寄りの base()
でなくJava寄りの super()
なんですね。
type Sushi = {}
と似た記法でinterface Shushi {}
が定義できる。- 違い1. 型エイリアスは右辺に
A | number
のように任意の型を指定できるが、インターフェースはこれができない。 - 違い2.
interface B extends A
と継承すると拡張元のメソッドの型をチェックする。型エイリアスの交差型は違う動きをする。 違い3. 同じスコープに同名の型エイリアスがあるとコンパイルエラー。同名のインターフェースがあると「宣言のマージ」をしてくれてひとつになる。
インターフェースはメソッドだけでなくインスタンスプロパティも宣言できる。アクセス修飾子や
static
は だめ、readonly
は指定できる。インターフェースは形状
(Shape)
をモデル化するための方法で汎用、軽量。コンパイル時まで存在してJavaScriptコードにはならない。このクラスはAnimalである、などを表現する軽量な方法。- 抽象クラスはクラスをモデル化、コンパイル後もJavaScriptコードになる。機能豊富。複数のクラスで実装を共有する時に使う。(親クラスで処理を共通化とか)
インターフェイスがマージされるとか、JSコードにならない話はやはりTS独特だな……と思います。
- TypeScriptではクラスは名前でなく構造によって比較、区別される。下の関数では引数のクラスが
walk()
メソッドを持ってさえいれば何のクラスかは気にしない。なおフィールドのアクセス修飾子はチェックされる。
function takeAWalk(animal: Zebra) { animal.walk(); animal.orginName = ''; // privateになっていたらこれはだめ }
let a = 123;
は値を宣言、type a = number;
は型を宣言。TSでは型と値の名前空間が別々。クラスと列挙型はこの両方を生成する。class MyMap<K, V>
のようにジェネリック型をサポート。普通にポリモーフィズムが表現できる。- mixinの構文や機能はないが、実装で実現可能。
- 実験的なデコレーターの機能があるが将来は不明。ECMAScript仕様でも未定のため。
- finalキーワードはないが、コンストラクタを
private
にすると拡張を禁止にできる。このケースではstatic create()
などでインスタンスを返すメソッドを追加すればよい。 - これらの機能を使ってデザインパターンも実現できる。
当方オブジェクト指向やデザパタの原理主義者ではありませんが、このへんのことも書けるのを見ると静かに感動します。そしてファクトリーパターンの例がちょっと分かりませんでした。練習問題の方は分かるのですが本文のコード、new クラス名 の後になぜ()がないのだろう……?
6章 高度な型
Type周りのさらに高度な話の章。
6.1 型の間の関係
- A, Bの型があり、Aのサブタイプ(派生型,
subtype
)がBなら、Aが要求されるところではBを安全に使える。 - A, Bの型があり、Bのスーパータイプ(上位型,
supertype
)がAなら、Bが要求されるところではAを安全に使える。 B <: A
がBはAのサブタイプか同じ型。A >: B
がAはBのスーパータイプか同じ型。- 4種類の変性が不変性、共変性、反変性、双変性でよく使うのが共変性、
<:T
- クラスの場合はまあ分かるが、関数が難しい。関数AのサブタイプがBであると言える場合、this型とパラメーター(引数)は
A>:B
、戻り値の型はA<:B
が条件。 const c = true;
はtrue
型、let c = true;
はboolean
型なのは、TSが型を拡大してくれるから。let c: true = true;
と明示的に型を示すとtrue
型となり拡大を防げる。let c = {x: 1} as const;
のようにas const
を付けると、型の拡大を防ぎかつreadonlyにしてくれる。type Options = {a: string, b:string ....}
のように定義した型のプロパティ名をタイプミスしてもTSが教えてくれるのは、過剰プロパティチェックという機能が自動で動くから。
6.2 完全性
type Weekday = 'mon'|'tue'| ....
のようにしてこれを関数の戻り値にすると、分岐が足りない場合は警告してくれる。tsconfig.json の"noImplicitReturns": true
にする。
6.3 高度なオブジェクト型
- Web APIの戻り値が複雑な構造のJSオブジェクトの場合、
type FriendListNumber = APIResponse['user']['friendList']['count'];
のようにJSの書き方と似た形式で型を認識できる「ルックアップ」がある。 type Keys = keyof APIResponse; // 'user'|'hoge'
のようにkeyof演算子を使うと、オブジェクトの全てのキー一覧が入ったunion型を作れる。let day: Record<WeekDay, day> = {};
のようにunion型を指定すると制約を引き継ぐことができるRecord型。- もっと便利なのがMap型で独自構文がある。予めよく使うものがTS自体に組み込まれている。
Partial<T>
,Required<T>
,Readonly<T>
,Pick<T,Keys>
など。 - 同じ名前のオブジェクトとクラスを両方使える「コンパニオンオブジェクトパターン」
このへんになるとぐんと難しくなってきます……! けっこう数学の領域に踏み込んできます。実際にコードを書いていると感覚的に分かってきそうな気もしますが、やっぱり高度な使い方は難しいなと。
type IsStringType<T> = T extends string ? true : false;
のように三項演算子的に型が分岐できる「条件型」- 条件の一部としてジェネリック型を宣言できる
infer
- あまり使わない方が良い、高度な逃避手段(エスケープパッチ)
- 例えばID文字列のように入る値にルールがある変数に値を強制できる型のブランド化のテクニックがある
- JavaScriptの言語自体のプロトタイプ拡張も、TypeScriptならより安全にできる
まとめにすべてのものを理解できていなかったり覚えられなかったりでも大丈夫、とあって安心しました。このへんはかなりTSを使い込まないと実感が湧いてこなさそうです。
7章 エラー処理
愉快なプログラマー小噺を冒頭に挟んで、TSのエラー処理パターンを論じる種。ユーザに誕生日の入力を促してそれを日付型にするfunction parse(birdtday: string)
関数を例にとって解説しています。
1==>
parse(birthday: string): Date | null
にして、中で異常が発生したらnullを返す。軽量な方法だが、呼び出し側にエラーの情報が渡らない。
2==>
parse(birthday: string): Date
にして、中で異常が発生したら throw new ThisError('エラー原因')
する。
呼び出し側は try {....} catch (e) {....}
で囲み、if (e instanceof ThisError)
で場合分けして処理。予め何種類かError
クラスがあるので拡張したクラスを作っておく。外側に情報が渡る。
関数のJSDocには@throws
で書いておいた方がよい。怠惰なエンジニアはtry-catch
を忘れる可能性がある。
3==>
parse(birthday: string): Date | ThisError | ThatError
と戻り値を列挙型にして、中で異常が発生したらthrowでなくreturn new ThisError('エラー原因')
と戻り値で返す。Javaのthrows
と似た形。
呼び出し側は if (result instanceof SomeError)
で場合分けして処理。コーディング時に処理が足りないとTSがTypeErrorを出してくれるので、TSの強みを活かせる。
4==>
自前で Option<T>, Some<T>
を実装。flatMap
やgetOrElse
メソッドを作り、失敗する可能性なる一連の操作をうまく扱える。
最後のOptions
型というのが高度で理解しきれませんでした……あまり使っているところを聞きませんが、日本のTSの現場ではどうなのでしょうね。エラー処理の方法は完全にあなた次第とあり、やはり3番目の例外を戻り値で返す方法かなあと思います。(try-catchネストも減るし。)
最後のOptionの話でHaskell言語より優秀だぜ!(ドヤ!)と本文ではTSをとことんアゲつつ、訳注でそうでもないですと冷静に書いてあったりして(笑)、このへんは公平でありがたいです。
8章 非同期プログラミングと並行、並列処理
8.1 JavaScriptのイベントループ
setTimeout()
の例を載せ、JavaScriptの内部的な動きを解説しています。
8.2 コールバックの処理
node.jsを使った非同期プログラミングでは、関数呼び出しの引数にコールバック関数を使う。しかしネストして複雑になっていくと途端に難しくなる話。本書では「コールバック地獄」でなく「コールバックピラミッド」というワードを使っています。
8.3 プロミスを使って健全さを取り戻す
これを解決する方法がお馴染みPromise
。この節では練習問題として、型指定を活用しながらPromiseクラスを自作する方法が書いてあります。
8.4 asyncとawait
外側の関数をasync function getFoo()
, その中の各処理をawait getBar() ....
と書いていくと、try-catch-finallyで上から同期的に順番に実行するように書ける話。TypeScriptでもこの構文を完全にフォロー。
8.5 非同期ストリーム
さまざまな型のデータが非同期にどんどん溜まっていく場合に使うデザインパターンであり実際のライブラリもあるEventEmitter
周り。TypeScriptではこれらを作る際に型を指定して安全に定義できる話。
8.6 型安全なマルチスレッディング
JavaScriptでは1つのスレッド内を分割してうまく処理しますがスレッドが複数になる本物の並列処理の話。
- ブラウザ上でマルチスレッドのやり取りをする
Web Worker
のメッセージパッシングでも型を活用する - npmで
@types/node
をインストール、Node.jsの子プロセスを使う際もTSで型を使う
この章も難しく感じました……!(ぐぬぬ) JavaScript自体の非同期周りを復習しないとなあ。
9章 フロントエンドとバックエンドのフレームワーク
お待ちかねフレームワーク群とTypeScriptの章。本書はTS自体の本なので、フレームワークについてはあまり多くは語られていません。
9.1 フロントエンドのフレームワーク
- すべてのJavaScript組み込みDOM APIが型安全で使え、コーディング中に警告してくれる。tsconfig.jsonを以下のように設定しておく。
{ "compileOptions": { "lib": ["dom", "es2015"] } }
- ReactにTSを使うのは素晴らしい選択。tsconfig.jsonに以下のように設定。
esModuleInterop
がtrue
だと、コンポ―ネント記述冒頭のインポート文はimport * as React from 'react';
でなくimport React from 'react';
になる。*.jsx
でなく*.tsx
ファイルに書く。
{ "compileOptions": { "esModuleInterop": true, "jsx": "react" } }
- 関数型コンポーネントはコンポーネント自体の引数(props)を
(props: Props)
として別途type Propを定義したり型を指定したり。TypeScriptのコンパイラがJSXの形式やコンポーネントに渡すプロパティの過不足をチェックし、強制してくれる。コンポーネントの宣言でconst app: React.FunctionComponent<Props>
のように書く最近のやり方は本書では記述なし。 従来のクラスコンポーネントも
props, state
を別途型を指定して書いたり。stateの中に存在するかのチェックや、this.state
に直接書き込んでいないかなどもコンパイラが弾いてくれる。React自体のPropTypes
機能はいらなくなる。Angular6,7。Angular自体がTSで書かれている。
- Angularのライフサイクルフックとして用意されている関数群(
OnInit
など)がTSのインターフェースになっている。 - クラスやオブジェクトの前に
@
つきでメタデータを記述する「デコレーター」はTypeScriptの実験的な機能だが、Angularはこれを多用している。サービスで依存性注入(DI)を可能にしている部分でもデコレーターを活用。
9.2 型安全なAPI
- 通信でJSONを渡すところなどを、TypeScriptの
union
型で工夫することもできる。 - API仕様から型が付けられたコードを生成するツールあり。
Swagger
のCodegen
,GraphQL
のAppolo
とRelay
、RPC
用のgRPC
とApache Thrift
。
9.3 バックエンドのフレームワーク
- バックエンド(サーバーサイド)をJS/TSで書く際、DBアクセスにもnpmにライブラリがある。
node-postgres
、node-mongodb-native
など。生でSQLを書くが、TSならこれらの戻り値に型が書ける。 - TypeScriptのORMツールでは
TypeORM
が有名。ほとんどのRDB、MongoDB
に対応。型安全な高レベルAPIになっている。
日本語版で2020年刊行の本なのでReactについてはクラスコンポーネントよりまず先に関数型コンポーネントを紹介、useState
をちゃんと使っていました。
Angular
はあるのにVue.js
がないのは、TypeScriptとあまり親和性が高くないからでしょうか。Vue.js3
でTypeScript対応が強化されたのは2020年秋なので、さすがに本書には間に合っていませんね。
10章 名前空間とモジュール
10.1 JavaScriptモジュールの簡単な歴史
- 1995年リリース時のJavaScriptはモジュールシステム一切なし、すべてがグローバル名前空間でデンジャラス。
- グローバルな
window
オブジェクトにwindow.someModule = ....
のように割り当てる即時実行関数式で工夫したりしていた。 - その後2004年に
Dojo
、2005年にYUI
、2009にLABjs
がモジュールローダーの仕組みを考案。 - 2009年の
Node.js
はこれらの教訓からモジュールシステムを組み込むことを決定。CommonJS
というモジュール標準を使用。 Dojo
とRequireJS
が推進したAMD
というモジュールもあった。- 2011年に
Browserify
が登場、CommomnJS
をフロントエンドで使えるようになった。しかし呼び出しが常に同期的、静的解析できない場合があるなど欠点あり。 - ES2015(ES6)ではインポートとエクスポートの標準を導入。これが現在の標準。TypeScriptでも使っている。他の方法も用意されている。
こういう歴史が書いてあるのはありがたいです。2000年代というと僕はサーバーサイドJavaを勉強してバシバシ使ってましたが、確かにDojoとか一時期あったなぁ……
10.2 インポート、エクスポート
- TypeScriptでもJS標準の記法を使う。
// a.ts 内 export function func1() {} export function func2() {} // b.ts 内 import {func1, func2} from './a' func1() // c.ts デフォルトエクスポート export default function def_func() {} // d.ts デフォルトエクスポートからの場合はimportの後の{}がいらない。 import def_func from './c' def_func() // e.ts ワイルドカードで全てインポート import * as a from './a' a.func1() a.func2() export * from './a' // 再エクスポート
- TSでは値と型が別の名前空間に存在するので、同じひとつの変数に入れても別扱いで推論してくれる。
// g.ts export let X = 1 export type X = {message: string} // h.ts import {X} from './g' let aaa = X + 1 // 値Xが使われる let bbb: X = {message: 'Hello'} // 型Xが使われる
let locale = await import('locale_ja')
のようにして遅延読み込みができる。tsconfig.jsonを変更。- tsconfig.jsonを変えて
CommonJS
やAMD
標準のモジュールも一応呼べる。 - 基本的にモジュールモードが動くが、一応スクリプトモードというものもある。
10.3 名前空間
naemspace Ns1 { export namespace Sub1 { export function getFoo(): string { } } export namespace Sub2 { export function getBar(): string { } } } // 同一の名前空間が複数ファイルにまたがっていても、コンパイル時にマージしてくれる。(!) // importした別のtsファイル Ns1.Sub1.getFoo() // 名前空間エイリアスという別名も使える。 import d = Ns1.Sub2.getBar d()
- 名前空間が競合するとコンパイルエラーになる。
- 明示的な依存関係があると読みやすく、モジュール分離を強制させ、静的解析が楽になる。複数ファイル分割はパフォーマンスでも有効。中規模以上のプロジェクトでは名前空間よりもモジュールを使うことを推奨。
10.4 宣言のマージ
同名の値と型、同名の名前空間、同名のインターフェイスがマージされる話を表で一覧化してくれています。
Udemyの講座でTypeScriptを実際に触った際、名前空間の中にクラスを入れて書くとインデントが1つ増えてあまり嬉しくないし、この記法はC#ぽいなあと思いました。
本書では明確に名前空間よりモジュールで分ける方を勧めています。JSのお作法的にもやっぱりそうなんですね。
11章 JavaScriptとの相互運用
既にJavaScriptのコードベースを使っている方には嬉しい、実践的なTypeScriptへの移行の章。
11.1 型宣言
*.ts
に対応した*.d.ts
が型宣言dファイル。declare
キーワードを使って型宣言だけを行っている。- 事前に明示的にインポートせずともグローバル変数を使えるようにするのをアンビエント変数宣言という。例えばNode.jsに出てくる変数
process
だったらpolyfill.ts
の中で定義されている。 - 同じく型を宣言するのをアンビエント型宣言という。スクリプトモードの
.ts
,.d.ts
の中で宣言。 - 同様にアンビエントモジュール宣言というのもある。
11.2 JavaScriptからTypeScriptへの漸進的な移行
- 1::: tsconfig.jsonの
compilerOptions
で"allowJs": true
にする。コンパイラTSCがJSも一緒にコンパイルしてくれるようになる。 - 2-a:::
"checkJs": true
にするとTSがJavaScriptコードについても能な限り型推論を試みてくれる。大体はany
になる。ファイルの先頭に// @ts-nocheck
,// @ts-check
を付けておくと判別してくれる。 - 2-b::: JSコードの関数の上の行に
JSDoc
をちゃんと書いておくと、これも型推論に使ってくれる。 - 3:::
*.js
ファイルを*.ts
ファイルに変えていく。エラーを正しく潰しながら1ファイルづつ進む方法、最初はコンパイラーを緩くして全体を移行してから対応する2つの方法がある。 - 4::: 最終的には
"allowJs": false, "checkJs": false
にして厳格にする。TSだけの世界に移行完了!
11.3 JavaScriptの型の探索
- 同一プロジェクトのJSコードでは、
.js
ファイルと同じディレクトリにある.d.ts
を探しに行く。 - npmから入れたライブラリは、まず
package.json
の中を見て、次にnode_modules/@types/{ライブラリ名}
を探しに行く。後述のDefnitelyTyped
プロジェクトで型を定義してくれている。
11.4 サードパーティーJavaScriptの使用
- 有名なライブラリは中に型宣言を備えていて、
compilerOptions
が"noImplicitAny": true
でもそのまま使える。 - そうでない場合はOSSの
DefnitelyTyped
で誰かが定義してくれている。@types
スコープの下で公開されているので、> nom install @types/react
のようにしてインストールすると使える。 DefnitelyTyped
にも定義されていなかったら、自力でやってコントリビュートするとよい。
Udemyの講座でTypeScriptを弄っていると謎の.d.ts
ファイルが確かに出現してくるのですが、なるほど型宣言のファイルだったのですね。@types/react
とか何だろうと思っていたのですが様々なライブラリの型宣言自体が別のOSSになっていたのか……!
既存のJavaScriptエコシステムとうまく調和するように作られているのだなあと思いました。
小ネタとしては映画『ウィロー』からJavaScriptの魔女バブモーダのネタがまた出てきます。(弟子の名前、ググっても出てこない……)
12章 TypeScriptのビルドと実行
最後はビルド周りの章。
12.1 TypeScriptプロジェクトのビルド
src/
配下にフォルダ構造を作って*.ts
ファイルをコーディング、dist/
配下にフォルダ構造そのままにコンパイル後の*.js
と*.d.ts
を置くのがお作法。なるべくこれに従う。- コンパイラの設定を変えるとJSファイルの
*.js.map
のソースマップ、*.d.ts
の型宣言、そしてコンパイル時間短縮の宣言マップである*.d.ts.map
も出力できる。ソースマップがあるとChrome開発者ツールなどが認識してくれる。 - バックエンドでNode.jsでJSを実行する際は、JSバージョンは自由。
- バックエンド用のOSSライブラリとしてリリースする場合は、対応するNode.jsのバージョンを書くぐらい。
- ブラウザ上で動くJSの場合が厄介。ポリフィルを使ったり、古いブラウザにメッセージを出したり。
- ブラウザ上でもバックエンドでも両方動くアイソモーフィック
(isomorphic)
なライブラリは、Node.jsの最低バージョンを保証し、かつブラウザの様々なバージョンに対応できるよう広い対応が必要。 - JSのバージョンは、
compilerOptions
の"target"
を指定。es3, es5, es6かes2015, es2016, es2017, es2018, esnext
。迷ったらes5
。 - JSの新しい機能を振るいバージョンにポリフィルする必要がある時は、
compilerOptions
のlib
に文字列配列で指定。ブラウザ上で動かす場合は"dom"
も指定。 - TSを幾つものプロジェクトに分ける場合はプロジェクトごとにtsconfig.jsonを作り、オプションに書く。
12.2 TypeScriptをサーバー上で実行する
compilerOptons
の"module": "commonjs"
にし、"target": "es2015"
にする。import
とexport
が使えるようになる。
12.3 TypeScriptをブラウザー内で実行する
12.4 TypeScriptコードをnpmに公開する
- 他の人が型を使えるよう型宣言を生成したり考慮が必要。npmにはコンパイル後のjsファイルだけ上がり、git上はコンパイル前のtsファイルだけを上げるよう、
.npmignore
,.gitignore
ファイルを修正するとよい。
やはり細かな設定はいろいろありますね。なるべくcreate-react-appとかNext.jsなどのデフォルトのお作法に従った方がいいのだろうなあと推測しました。
13章 終わりに
最後のまとめの後は40ページにもわたる圧巻の付録が付いています。型の話やオプション一覧など、実際に使う時のお役立ち情報が満載です。
そしてお得なのが日本語版オリジナル、翻訳の今村謙士さんによる「付録H ESLintとAST」。TSLintが2019年に非推奨となった後、JSと同じようにTSでもデフォルトになったESLintの使い方、独自ルールの作り方が、かなり細かいレベルで解説されています。これもありがたいですね。
まとめ:満を持して登場の、日本語で読める最新のTSの書
基礎から応用まで、さすがにオライリーの本はしっかり一通り書かれているな……という印象です。元からユーモア交じりの砕けた文章なのですが文章は読みやすく、日本語訳も自然でそれほど引っかかるところはありません。あまり日本語で読めるまとまった本がない中で、最新の一式揃った本格本として、1冊手元に置いておくと学びになると思います。注釈の中には日本語版独自の訳注もあってありがたいです。
欠点は特にないのですが、あえて言うと時々出没するJavaScriptの魔女バブモーダを始め、日本人からすると言い回しが分かりにかったりする英語特有の謎のジョークがところどころ出てきます。(ムッソリーニって確かに悪い人だけど技術書の中で冗談に使うもんなの?と思ったりしました。)
作者のBoris ChernyさんはかのFacebookでご活躍中のエンジニアでTypeScriptが大好きなんだなというのはよく分かったのですが、「Javaを使っている友人に自慢しましょう!」「Haskellの(ry」みたいな、日本で下手にネットでイキッて吹聴したら叩かれそうな表現も所々出てきます。まあこれは言語を推進する立場からのポジショントークというか、アメリカの一流エンジニアでもこういうもんなんだなぁと思うことにしました……(笑)
TypeScript言語自体の方はまだ業務で本格導入はしていないのでUdemyの講座をやったりしているのですが、ドットを打った時の補完とか、間違うとすばやく出てくるエラーとか修正候補の表示とか、実際にVSCodeでコードを書くとそのありがたみが分かりますね。
僕自身はJavaScriptがまだいらない子だった時代からWebサイトでいじったり、仕事の方はメインはJavaやC#など型の制約の強い世界でキャリアの基礎を固めてきました。そういう視点からすると、おお……ちゃんとクラスも継承も使えてデザインパターンとか使えるじゃん……と思ったり、フリーダムなJavaScriptもほんとはこういう動き方をするようにちゃんと厳格に育ってほしかったんだよ!という点がTypeScriptだと実現されていたり、謎の感動を味わいました。
それでいてエラーメッセージで賢く助けてくれるのはコンパイルされるまでで、その後は動く実物の中では動的なJavaScriptに戻って実行される。厳格さと柔軟さを兼ね備えた新時代の言語。
いやはや面白いこと考えたものです。新しい考え方というのはどんどん出てくるのだなと改めて感じました。
リンク集
オライリーのページ。かなりはてブがついて注目されました。 www.oreilly.co.jp
本書のGitHubリポジトリ。正誤表に飛べます。 github.com
作者のBoris CherryさんのブログとGitHub。 https://performancejs.com/performancejs.com github.com
ネットの感想では以下のあたりが見つかります。
- 【JS完全に理解した】JavaScript PrimerとプログラミングTypeScriptとレガシーフロントエンド安全改善ガイドを読んでみた - give IT a try
- "プログラミング TypeScript" を TS 初心者が読んだら点と点とが繋がった - ささきしぶろぐ
- 【書評】プログラミングTypeScript 〜第3章〜 | ryokatsu.dev
- 『プログラミングTypeScript』を読んだ - 30歳からのプログラミング
- プログラミングTypescript読んだ | garsue.dev
- 2020年8冊目:プログラミングTypeScript ースケールするJavaScriptアプリケーション開発 - きよきよログ
- TypeScript の勉強のために『プログラミング TypeScript』を読んでいる | 35d
- TypeScriptについて勉強したメモ - どこにでもいるSEの備忘録
- プログラミング TypeScript スケールするJavaScriptアプリケーション開発のレビュー(読みながら) - Qiita
日本語で読めるTypeScriptの本
Kindleのみですが『速習 TypeScript 第2版 速習シリーズ』が2020年3月。僕も読みましたがこの本は薄く、速く読めて概要を掴むにはお勧めです。
ディー・エヌ・エーの吉井 健文さんによる『実践TypeScript ~BFFとNext.js&Nuxt.jsの型定義~』が2019年。この本もよく聞きます。
他、翻訳本の『TypeScript実践プログラミング (Programmer's SELECTION)』が2015年、『JavaScriptプログラマのための 実践的TypeScript入門』が2014年。他にも見つかりますが、このへんになるともう情報が古いので非推奨ですかね。
TypeScript, Reactをはじめとするフロントエンドフレームワークの入門本まとめ記事最新版が、このブログ内のこちらにあります。