プロを目指す人のための TypeScript 入門を読んだ話


「プロを目指す人のための TypeScript 入門 安全なコードの書き方から高度な型の使い方まで」、通称ブルーベリー本を読んだので読書メモを書いておく。

https://www.amazon.co.jp/dp/B09Y527YPV

読者である私自身の技術レベルは以下の通り。

  • 2020 年からプログラミングを始めたので、最近の垢抜けた TypeScript/JavaScript しか知らない
  • 2 年くらい仕事で React/Node.js を使っており TypeScript を書いている
  • 型の表現に自信がないので、自分ではあまり複雑な型を書かない

入門とはいえ分厚い本なので全てを紹介することはできず、自分の印象に残った箇所に止める。

この本の位置付け

TypeScript(や React, Node.js)で実践的なアプリケーションを作ろう!という書籍ではなく、その仕様や使い方を詳細に紹介する書籍。

基本的な文法の解説もされているが、プログラミング初学者が読む本ではないと思う。というのも、この本が提供する「TypeScript を使って型安全なプログラムを書くにはどうしたらいいか」という情報そのものが初学者にとって興味が湧かないと考えられるためだ(初学者であっても型安全の恩恵は受けるに違いないのだが)。

一方で、仕事や趣味である程度 TypeScript を書いており「何となく動いてるけどこれ裏側ではどうなってるのかな」と気になっている人、「TypeScript でより型安全なプログラムを書きたい」と思ってる人、もしくは JavaScript を書いており「TypeScript ってどういう言語なのか知りたい」という人に非常に役に立つ本であることは間違いない。

JavaScript を読み書きしたことがない人はまず JavaScript Primer などを読んでからこの本を読んだ方が良い。

6 章を中心に TypeScript の特徴である型システムで高度な型を表現する方法を紹介している。「プロを目指す人のための〜」というタイトルにふさわしい。

第 1 章: イントロダクション

TypeScript の特徴や歴史の紹介、開発環境の整備。

TypeScript の型システムを利用することで、型チェックによるコンパイルエラー、ソースコードのドキュメント化、エディタや IDE による入力補完など様々なメリットを享受することができる。

TypeScript コンパイラは型チェックによりソースコードの文面から静的にプログラマの型注釈との矛盾をチェックし、JavaScript へトランスパイルする。静的チェックによりエディタや IDE での開発中に簡単にミスを発見し修正することができる。

JavaScript には静的な型チェックはなく、ランタイムの挙動は型情報に依存しない。JavaScript へのトランスパイルでは基本的に型情報を取り除く(後は一部の新しい構文を古い構文に変換する)だけで、プログラムの挙動は型によって変わることはない。つまり、ある程度 JavaScript の経験があるプログラマーにとっては型の扱いを覚えるだけでよいため非常に使いやすい言語となっている。

Node.js をインストールして簡単な環境設定(tsconfig)をする箇所については、tsconfig 自体が膨大なオプションがあり初学者にわかりづらい中で簡単な説明と後で詳しく解説する箇所へのリファレンスがついていて素晴らしかった。

第 2 章 ~ 第 5 章

  • 第 2 章: 基本的な文法・基本的な型
  • 第 3 章: オブジェクトの基本とオブジェクトの型
  • 第 4 章: TypeScript の関数
  • 第 5 章: TypeScript のクラス

TypeScript の文法や型、オブジェクト、関数、クラスなど基本的な知識に関する章。

既に仕事や趣味で TypeScript を書いていればこれらの章に書いてあることは基本的に日常のプログラミングで使っているので、退屈な章に見えるかもしれない。しかし、新たな再発見があるかもしれないし第 6 章の高度な型の前提となる知識でもあるので読み飛ばさず、ささっと読んでみることをおすすめしたい。

例えば自分の場合、第 3 章の computed property name (オブジェクトのプロパティ名を動的に決めるための構文)については全く知らなかった。他にもこんな機能があったのか!という記述がそこかしこにあり、自分の知識の欠けていた箇所を見つけ出すことができた。

「4.2.3 返り値の型注釈は省略すべきか」という項も面白かった。TypeScript では型推論によって関数の返り値を調べており、もし返り値の型が代入先と合わない(例えば number[] を返さなければいけないのに何も返してなかった)時には当然コンパイルエラーを出してくれる。しかし、関数の返り値の型注釈を書いていれば関数内部でコンパイルエラーを出すことができるのに対し、書かない場合は関数を呼び出して結果を使う側でコンパイルエラーが出る。基本的には関数の返り値の型注釈を書く方がエラーを発見しやすくなるので望ましいだろう。

このように、TypeScript における型注釈には「開発者による真実の源としての型の表明」という意図を表している。つまり、型を明示しないということは呼び出し側が型や実行時の動作に責任を持つことを意味する。型注釈や型定義を書くときはプログラムの構造における真実の源をどこに置くべきか考えながら書くのが良さそうだ。

型以外にも式と文の違いなど、普段プログラミングをする上ではあまり意識しない(?)ような前提となる事柄も丹念に説明されており、「プロを目指す人のための〜」というタイトルを思い起こさせる。「プロ」という言葉をどう捉えるかはその人次第だが、自分は「基礎から応用まで幅広い知識を持ち状況に応じて問題を解決するための最適な選択肢を導き出せる人」だと考えているので、この本に垣間見える用語の正確さへのこだわりであったり基礎的な部分を疎かにしない姿勢にはプロを感じる。

プログラマー歴 2 年かそこらの自分が偉そうに語れる話でもないのだが…

第 6 章: 高度な型

この本で最も重要な章かもしれない。ユニオン型とインターセクション型、リテラル型、代数的データ型などの解説。

関数のオプショナルチェイニング(getTimeFunc?.() のような書き方)が紹介されてたのだが、これできるの初めて知った(変数でしか使ったことなかった)。

keyoftypeof を使った型レベルの計算はなかなか面白い。TypeScript 以外の言語ではあまり見たことない気がする(あったらすみません)。これによって型の表現力が大きく高まっているように見える。

オブジェクトにリテラル型の tag プロパティを用意して扱うデータの形と可能性を型として正確に表現する、そしてそれを使って if や switch 文で型の絞り込みを行うという方法は便利そうなので覚えておきたい。

型アサーション(as で型を上書きするやつ)はランタイムエラーを引き起こす可能性があるのでなるべく使わない。使う場合は不正確な型を正しく直すために使うべきである(つまり、TypeScript の型推論を補助するために使うべきで刃向かうために使うべきではない)。

any 型は型安全性を破壊するので避けるべき。型チェックが行われないのでコンパイルで発見できたはずのエラーもランタイムエラーとなり、 TypeScript を使う意味が薄れてしまう。依存先が JavaScript から TypeScript に移行中で any を使っていたのが型を付けるようになりその型を使わないとコンパイルで引っかかるという落とし穴もあるので迷惑な存在だが、これによって TypeScript への移行が早まったという背景もあると思うので必要悪的な存在なのかもしれない。

ユーザー定義型ガードや型アサーションも同様だが、TypeScript が提供する型安全性を犠牲にしてプログラマの責任とすることを十分に意識すべき。

第 7 章: TypeScript のモジュールシステム

Import / export でデータや関数を受け渡す方法について。

簡単な変数や関数、型の export から始めて、モジュール間でデータや関数を受け渡す方法が解説されている。

Default export 使わない理由(明確な名前がないので補完が働かない)というのは確かになと思った(私も基本的に使わないのだが)。

スクリプトとモジュールの違い、ES Modules と CommonJS Modules の違いなど、普段意識しないところまで書かれていてありがたい。特に ES Modules は最近会社のプロジェクトの依存関係を更新していて詰まることが多かったので個人的にタイムリーな話題だった。具体的には今まで CommonJS だったモジュールがあるバージョンから ESM only になるとか。

第 8 章: 非同期処理

この章については JavaScript における非同期処理(通信など時間がかかったり、ファイルへの保存など I/O を伴うため即時に完了できない処理)を基本から説明している。

Promise の理解があやしい人は JavaScript Promise の本 もあわせて読むのがおすすめ。

TypeScript 特有の注意点として挙げられていたのが、 Promise 失敗時のコールバック関数に渡される引数 error の型が any となってしまい型システム上はどのようなエラーが生じるか明らかでないという仕様がある。なんで unknown じゃなくて any なんだろうと疑問だが、歴史的経緯によるものだそうだ。

何でも入るという点では any と同じだができることが限られる(プロパティアクセスができなくなる) unknown 型を注釈に入れることで少しだけ安全になる。これも意識したことなかったけど型安全性を意識するなら可能な限り any を使うべきではないので覚えておきたい。

第 9 章: TypeScript のコンパイラオプション

TypeScript のコンパイル時の挙動を制御するオプション(tsconfig.json)について。

環境構築する際に package.json や tsconfig.json を書くのだが、基本的に他のプロジェクトやよくある設定のやつをコピペしてドキュメントを軽く読むだけであまり幅広いオプションを利用してこなかった。strict を絶対 true にするくらい。

TypeScript の型チェックは後方互換性を重視しており、厳しいオプションは明示的に設定しなければ有効とならない。つまり、既存のプロジェクトからコピペすると(後方互換性を考えなくていいにも関わらず)古い書き方をそのまま使ってしまうので、コンパイラオプションが緩くなりがちになるなーと思った。

noUncheckedIndexedAccess(インデックスシグネチャを通じたプロパティアクセスで得られる値を常に undefined とのユニオン型にする)と exactOptionalPropertyTypes (オプショナルプロパティに明示的に undefined を代入することができなくなる)についても知らなかった。

コンパイラオプションを羅列して解説するというよりは、基本的な有効にすべきオプションの挙動の説明とコンパイラオプションの立ち位置を。後から厳しくするのは難しいので最初から厳しくしておくというのは良さそう。実際、コンパイルエラーをちゃんと出してくれた方が結果的に速く開発が進むため。

感想

この本を読んで TypeScript の型との向き合い方が変わったかもしれない。

今まではプロジェクトの既存のコンパイラオプションや型定義、開発メンバーで定めたガイドラインに沿ってプログラムを書いていたが、積極的に新情報を調べたりより型安全なプログラムを書くための工夫を探求していこうと思った。