「プロを目指す人のためのTypeScript入門」読書感想
@uhyo さんのTS本ということで読んでいます。
一応入門書という位置づけなので、社内での育成に活用したい目線。
とはいえ自分でも学べるところ多そう、特にTSのd.tsみたいなモジュールまわり理解弱いので…
全体的な感想
4章(関数)、6章(高度な型)が特によかった。
(5章が入ってないのは自分がクラス使わないからなので、使う人だったら5章も入ってると思う)
自分も何年かフロントエンドエンジニアとしてやっているのでさすがにだいたいは知っている内容のおさらいになったけど、それでもふわっとした理解が適切に言語化だったり、コーナーケースの知らない挙動など発見は沢山あった。
特に育成には有用だなと思った。6章の内容は理論と実践の話のバランスが良く、レベル的にもそれなりにTS書いてる人でも知らないこと全然ありそうな内容だったので、自分のチームのメンバーにも少なくとも6章は読んでもらいたい。勧めます。
TS本というよりはJS+TS本。TS自体がJSのスーパーセットだから当たり前といえばそうだけど…
JSのリファレンス本にもなるので、1冊持っておけばOK感があってよいなと。
初学者のときは仕様書にあたるのもサイ本みたいないかつい本めくるのも大変だと思うので、それらのもう一段階カジュアルな位置づけとして活躍しそうな本だと思いました。
カジュアルとはいえそれらと並べたぐらいに説明はすごく丁寧で厳密なので、そのぶん情報量が多く知らないことを知るための入りとしては難しい概念では圧倒されたりもしそう。読んで学ぶよりも実践が好きな人は、むしろ力試しから入ってみてわからないところを目次や索引見ながら説明読む、という活用もできそうと思った。
逆に丁寧だからこそある程度わかっている人のおさらいにはとてもよいと思う。脚注読むのが楽しかった。
じっくり読めたことで私のTS力も少しuhyoifyされたかもしれない(?)
1章 イントロダクション
だいたい知っていることのおさらいではあったものの、静的型付き言語って何が嬉しいの?という人には良い説明になりそう。
コンパイルエラーへの向き合い方が書いてあるのがよかった。このへん特に初心者に伝えたいところだと思う。
第三者がコードを書くところを見る機会が時々あるけど、赤い波線が出ているときにエラーメッセージを読まずに周りのコードをあちこち触りだすパターンは少なくない。早い時期に正しいエラーとの付き合い方を学べるとその後の伸びの角度が変わりそう。
エラーメッセージ英語のままのほうが検索するときに情報が出てきやすいよとかもわかる。(こういう補足あると、エラーってそのメッセージそのまま検索すれば何か情報出てくるんだ…というのも伝えられていいですね)
型によるドキュメント化たしかに…
自分は関数書くとき、返り値の型は推論頼りにしちゃうことが多い。それで実務上困ったことはないけど、ドキュメント的な視点を入れると、返り値の型を明示的に書くことで他人が読む時に 関数名+引数型+返り値型 でだいたいのイメージがついて良いみたいな恩恵がありそう。
ECMAScriptのStageとかの解説も入ってる。Stage3がTS入りの条件っていうのはあんまり認識してなかった。
enumみたいなTS独自機能は使うべきでない、という主張は振り切ってますね。主張の内容には私も同意なのですが、そもそも技術書で自分の考えを言い切るのって結構勇気がいる(経験談)ので、きっぱりした表明いいなと思いました。
開発環境セットアップ。こう見るとTS、最小限構成で書けるようになるまでがめんどくさいね…実際はクライアント向けならviteとかでテンプレ立ち上げしちゃうんだろうけど、技術書には書けないしなぁ。
tsconfigにも説明があっていい。
JSONは本来コメントの機能がありませんが、TypeScriptでは特別にコメント入りのJSONを解釈できるようになっています。
そうだったのか…!
2章 基本的な文法・基本的な型
基本的な文法の紹介。
かなり厳密・正確に説明されているので、本当に最初の一歩として学びたい人はいきなりここから学ぼうとすると圧倒されちゃう?もっとゆるふわな解説でざっくり把握してしばらくコード書いてみて、ある程度各構文や利用頻度をざっくり体感してから読むのがいいかも。
逆にもう基本的な構文には慣れているが厳密なことはあまり知らない、みたいなエンジニアが読むと、新しい発見や納得が色々ありそう。
文と式の違い!このへん仕様定義にはそのまま出てくるので読む時は理解必要だったりするんだけど、自分の理解結構曖昧だったんだよな。
式には結果があり、文には結果がない。式文は式のあとにセミコロンを書く文。
このへんはJSと共通だけど、「TypeScriptにおいて、式は常に何らかの型を持ちます。」っていうのがTSでの追加のポイントかなー。(静的型)
==
や if のあたりで暗黙の型変換の話がされているの、理解しやすくて良さそう。
そういえばbigint使ったことないなぁ…
-1
でリテラルじゃなくて -
式 + 1
リテラルなんだ…(じゃー別のとこで見た 0
+0
-0
が同じ値?みたいなやつも、そもそもリテラルの比較の話じゃなく式結果の比較だったんだなー)
switchのbreak忘れってTypeScriptのオプションにもあったんだ…全然知らなかった。eslint:recommendedに no-fallthrough が入ってるのでそちらでも拾えてはいそうだけど。どっちかはいれときたいやつ。
力試しがあるの、とてもよき!
3章 オブジェクトの基本とオブジェクトの型
JavaScriptのオブジェクトの仕様から丁寧に解説してある。
インデックスシグネチャ便利だけど危険なんですよね。いま頑張ってnoUncheckedIndexAccessオプションONにしようとしてる…
Map使いたいけど参照が同じになるしimmutableに作り直すのコンストラクタ呼び直さないといけなくて仰々しいからReactの文脈でいまいち使いづらいんだよなぁ。。Recordはよ〜
typeofのコラムよかったです。「何が最上位の事実か」を考えようというやつ。
多くの場合で最上位に来るのは型です。これは、基本的に型は設計を表すものであり、実装は設計に依存して書かれるという点で型よりも下位にくるものだからです。
この言語化よき〜となりました。コードレビューとかで、このtypeofは、ぐぬぬ…となったときこのへん引用したいな。
値が上位にくるケースも実感あってよかった。
型引数とジェネリクスはイコールじゃないのか、「型引数を受け取る関数を作る機能」がジェネリクスなんだ。でも型引数を持つ型はジェネリック型って呼ぶのか。ややこいね…このへん定義知ってもちゃんと言葉使い分けられる自信がないなー
正規表現とかも触れるんですね。TSだけじゃなくてJSのリファレンス本の側面も結構あるなー。
4章 TypeScriptの関数
関数宣言と関数式とアロー関数式、わりと好みの問題かなと思ってたけど、こう見るとアロー関数式に結構実利がある感じなんだな。
自分もアロー関数式派だけど、好みの問題としか思ってなかった。
アローでオブジェクトリテラル返すやつ初心者のときハマりがちなので説明あってよき。
関数型に引数名が含まれるの、私も不思議だなと思ったことあったんだけど、たしかにドキュメントとして役立つのか…
インデックスシグネチャ({ [key: string]: value }
)の key: にも同じこと思ってた。そっちもそうなのか?
返り値の型注釈のところ、 "真実の源" ってワードかっこいい…
コールシグネチャ知らなかった!
あんま使い所ないかなと思ったけど、引数によって返り値変わる関数の定義に使えるのか。関数オーバーローディングでしかできないと思ってたから、関数式派には使いづらいなと思ってた。
積極的には使わないけど、こういう手もあると知っておくのはいいな。
脚注にあるのは https://twitter.com/uhyo_/status/1519243390118146055 みたいな話かな?(TSコミュニティのツイートなので入ってないと見れないかも)
てか関数オーバーローディングってもう説明省略されちゃうぐらいあんま使われてないんだ!ライブラリの型定義ではよく出てくる印象あるけど。アプリケーション書くときはあんま使わないか。(なんとなくTS玄人は普通に使ってるのかと思ってたので意外だった。けど関数の中身がだいぶ辛い感じになるもんな…)
関数引数の部分型関係の説明きた!私はここのエラーきっかけで変性の概念を知ったな…
概念ざっくり知った上でだと読んで理解できるんだけど、初見だと地の文だけから理解することできるかな 😂 図とかがあると初見でもとっかかり掴みやすいかもしれない。
とはいえここは文法みたいに一度読んだだけで完全理解できる類ではないと思うので、自分でも色々調べていくしかないですね…個人的にももっと理解掘り下げたいと思っていて、ここの理解はTSというより型システムの話だから型システムの話の本とかを読んだりすると掘り下げられるのかな?と思っている(がまだ読めていない)
ちょうどTS4.7で Variance Annotationsも入ったし、アツい話題そう。
型のメソッド記法で引数の型制限がゆるくなる(双変になる)の知らなかった!ていうか型でもメソッド記法できるの自体知らなかった気がする…知らなくて使ったことなかったからよかった。(結果論w
あー T[] と readonly T[] にも部分型関係があるのか。言われてみればだけどそれも認識してなかったな。
オブジェクトだと効かない、っていうのはオブジェクトのreadonlyがプロパティごとだからなのかな…オブジェクト全体にreadonlyってつけられたらこのへんも判別していけるんだろうか。Recordはよ〜
ジェネリクスの型引数の型を関数の引数から推論できるのは知ってたけど、文脈からも推論させられるっていうのは意識してなかった。
関数fが返り値だけにかかるような型引数Rを持ってたとき、 f<Hoge>()
で指定するんじゃなくて const hoge: Hoge = f()
でも推論させられるってことか。なるほどな〜
違う話に飛ぶけど、P175の脚注にある
引数xの型は型注釈によって決まるか、そうでなければあくまで文脈から決まるのであって、使われ方から決まるのではないのです。
っていう説明いいなと思いました。
5章 TypeScriptのクラス
クラスだー。
普段の実装だとクラスはカスタムエラー作る時ぐらいしか使わないけど、ベースの知識は頭に入ってるはずなのでそれを確かめる意味で読んでみました。
発見があったところを箇条書きに。
- コンストラクタに引数がない場合は
()
を省略できる(new Date()
はnew Date
でもいいのか) - クラス式。また、クラス式の場合は型は作られない
- てことは
const User = class {}
したらUser
の型は何なんだろ?と思ったらtypeof User
だった。循環定義じゃん… -
const u: User = {}
してみるとprototypeがないって怒られる
- てことは
- 静的初期化ブロック
static { ... }
- クラスそのものの型は
new (コンストラクタ引数) => インスタンス型
か{ new (コンストラクタ引数): インスタンス型 }
-
override
修飾子 +noImplicitOverride
オプションで安全にオーバーライドできる - 関数内のthisの型の明示は、引数リストの冒頭に
this: 型
で書ける -
#
のprivateプロパティはそのクラス内からしか参照できないが、クラスが同じなら別インスタンスのprivateプロパティも参照できる(P224のfilterOrder)
最初からTSで学んでいくと、クラスは値と型がごっちゃになりやすいところだと思うけど、コラムで名前空間の解説があってちゃんとおさえてあったのがよかった。
instanceofも、型チェックではなくてインスタンスかどうかのチェックっていうのはたしかにTSから学んでいくとごっちゃになりそう。実際はcatch内のError判別ぐらいでしか使わないけど…
JSの #
と TSの private
の差も整理してあってよかった!
ES20XX、クラス関連の機能だとスルーしてる自分に気付いた…ES2022どっかでさらったはずだけど静的初期化ブロックとか全然頭に残ってなかったよ。。
thisの説明は懐かしさ…アロー関数の恩恵ももう当たり前になりすぎて忘れかけてた。。 _this やってたな 😇
thisまわりでクラス使わなくてもやりがちなのが、たまにオブジェクトのメソッド内で同オブジェクトのプロパティやメソッドを参照したいときにthisでいけるやんけってなるんだけど、オブジェクトだと分割代入でメソッドとってきて単体で呼ばれるとか全然あるからそのとき参照できなくてハマりがち。静的エラー出ないのがきつい。クラス使わないなら基本的にthisは使わないほうがいいと思った。
クラス以外での発見
-
Reflect
組み込みオブジェクト-
Proxy
使ったことあるのにReflect
ノーマークだった…ES2015で入ってたぽい?
-
- finallyはthrowやreturnでの脱出に割り込める
- returnにも効くのか!例外扱うときだけのやつだと思ってたけど、独立した仕様だったのか
この章はTS抜きにしてもJSのクラス・this・例外あたりが一通りおさえられて読み応えありました。
そしてやっぱ継承とかオーバーライドとかはバグりやすすぎるから普段使ってなくてよかったなぁ…という感想になった。
6章 高度な型
ついにきた!さらにおもしろくなってきそう。
6.1 ユニオン型とインターセクション型
静的型付き言語は多くありますが、これらの機能を持つ言語はあまりありません。
そうなんだ!?
直和型ってタグ付きユニオンのことだったのか。
コラムの「型にない」と「実際にない」の話、良いな。私もまさに昔Unionでどっちかにあるプロパティはアクセスできてもよくない?って思ったことあるけど、こういう説明があると完全に納得できる。
「型にないプロパティについてはundefinedと決め打ちすることは不可能です」っていうのもうっかりしがちなところだと思う。これも構造的部分型の話から説明されてるから完全に納得できる。
関数型同士のユニオン型を作り、それを関数として呼び出す場合、引数の型としてインターセクション型が現れます。
たしかに!関数のユニオンあんまり扱わないから気付いたことなかった。
そのあとのコラムもめっちゃ面白い。反変の位置だからインターセクションになる。
optionalとundefined unionの使い分けの話も納得感あるな。
ただ引数渡す側で条件分岐してオプショナルにしたいとき、完全にプロパティ省略するのって結構めんどいから key: cond ? value : undefined
みたいにしがちだったりして、そことの食い合わせが悪いんだけど…
でもひとつの記法がひとつだけの意味っていうのは良いよなぁ。exactOptionalPropertyTypesいれようかな…
?.
でもundefinedだったら後続の処理が飛ばされるってことで型の絞り込みみたいな効果が得られるのか。
6.2 リテラル型
私リテラルとプリミティブという言葉を混同して使ってた場面があったかもしれん…
テンプレートリテラルの説明も入ってる!意外とすんなり。前段で説明に必要な用語の説明が全部終わっているからこれだけシンプルに説明できるんだろうな。(contextual typingとか)
リテラル型のwideningの説明、なんとなく直感で受け入れてた挙動が全部理論的に説明されている…ちょっとした感動をおぼえた。普段ふつうに使ってるのにこういう挙動になっていること自体意識してなかった気がする(TSの、使いやすさに寄せた設計がそれだけ功を奏してるということだと思うけど)
let宣言とオブジェクトプロパティでは値が再代入される可能性があるので、型推論のリテラル型は拡大される、と。
6.3 型の絞り込み
タグ付きユニオン便利。
代数的データ型、何のことかわからない。ぐぐってみたがわからない。Haskell…
あーswitchでの絞り込み、defaultでnever checkしなくてもそもそもdefault書かなかったらエラーにできるのか🤔 これは考えたことなかった。returnしない場合とかでもいけるかな?
6.4 keyof型・lookup型
T[K]
ってlookup型って名前だったんだ…
keyofは型レベル計算の第一歩!わかる。
keyof が string | number | symbol になるのは知ってたけど、stringだけにしたいときは & string
ってすればいいのか。 extends string とかやってたかも?
6.5 asによる型アサーション
asやめたいわかる。立ち上げ時はあんまり考えずに使っちゃってたけど、今は撲滅運動してる…
@typescript-eslint/consistent-type-assertions を ['error', { assertionStyle: 'never' }]
にするとeslintで弾けるので、これをONにできるように削減頑張ってる。多分もうすぐできる。(このeslintオプションもuhyoさんに教えてもらった)
as constの効果が列挙されてるのすばらしい!これもほんと普段使ってるけどなんとなく自分が目にする効果しか理解してないから…
6.6 any型とunknown型
最凶型any
6.7 さらに高度な型
ユーザー定義型ガードがここに入るのはちょっと意外だった。asよりbetterだからasと同等以上ぐらいの扱いかと思っていた🤔 難しいのかな?
never, mapped types, conditional typesが触れられているのはよかった!しれっとunion distributionにも触れられてる。このあたり、深い解説じゃなくても紹介がされているだけで、育成とかに使う場面を考えるとかなり助かる。(単語が通じるようになるし、紹介の先を自分で興味持って調べてくれる人もいるだろうし)
組み込み型、それなりに使ってるけど、Extractだけうまく使えていないな…ふたつめに渡すの、unionじゃなくてプリミティブとかobject型とかでもよかったんだ。それやりたかった場面ありそう。
コラムで紹介されてる、 nullでもundefinedでもないunknownを Record<string, unknown>
に変換するテク、めっちゃ有用。この記事で見てからさっそくパクらせてもらって、自分の場合はだいたいオブジェクトに絞りたい(nullとundefinedと一緒にプリミティブの可能性もまとめて捨てたい)ので以下のような関数ををutiltyとして定義して使ってる。
export const isObject = (data: unknown): data is Record<string, unknown> =>
typeof data === 'object' && data !== null
7章 TypeScriptのモジュールシステム
まずはESMの説明。
defaultってdefaultって名前のnamed exportされてるのと一緒なんだ。特別なのかと思ってた。
コラムわかるー。私もauto importのために、exportする変数名はuniqueになるようにしてる。
前はドメインモデル問わずhogeって処理をする関数はhogeって名前つけてたけど、それだとhogeでimportするとき競合するから、userHogeとかgroupHogeみたいにドメイン名も名前に含めるように切り替えた。
export type { Hoge }
知らなかった。ここに値も含めることができるのか!型の生成元になるようなリテラルタプルとかならありかなって思ったけど、やっぱtypeof済のほうを型としてexportしたい感じするから使い所わからんな…
値じゃなくて型を含めるときは export type Hoge = ...
と一緒の結果?
default export は export * from '...'
には含まれないのも知らなかったなー
- TSではスクリプトとモジュールの判別が自動
- スクリプトのときはトップレベルがグローバルスコープになる
これそういうことなんだ!なんかトップレベルにexportじゃない変数定義をするのに抵抗があったのは、このへんがごっちゃになっていたからかも。
型定義ファイルの作り方紹介されてるの嬉しい。 d.ts TS慣れてもしばらく一番よくわからなかったやつ。
import.meta.url
って初めて見た気がする。
8章 非同期処理
そういえば非同期まだだったのか。
シングルスレッドの説明ありがたや
Promise入ったのES2015なのかー。その前どうしてたっけ…jQuery.Deferredかー
この記事 とか読んでいた記憶ある!
あとBluebirdとか…
コールバックの説明見て、これがPromiseになってasync/awaitになってよかったなぁ…としみじみ。
アロー関数入ってコールバックも書きやすくなったけどねえ。
9章 TypeScriptのコンパイラオプション
オプションよくわからないの多いから嬉しい!
include/excludeはあくまで起点。
noUncheckedIndexAccessはほんとなー、入ったのが2020年下旬だったと思うので、立ち上げ時から入ってれば…!!と思ったやつ :cry:
今頑張ってONにできるよう対応進めてる…
私も一番厳しくしたい派なので、 こちらの記事 の tsconfig/strictest を導入しました。今ONにできないやつだけ上書きしてるけど、どれを上書きしてるのかがひと目でわかってよき。
付録
学習の道しるべ、すごくいい。
アクションしてみたいことリスト
- tsconfig.jsonにはコメントが許される → コメント入れて読みやすくしてみる
- 弊社のOSS eslint-plugin-strict-dependencies もtsconfig.jsonを読み込むので、さっそくコメントつきtsconfigにも対応させた 👍
- arrow functionでオーバーローディング的なことしたくなったらコールシグネチャを試してみる
- contextual typingを意識する
- Proxy使うときはReflectを使う
- exactOptionalPropertyTypesいれようかな…
- union以外でもExtractを使う
- 付録の学習の道しるべを調べてみる