「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」を 2,023 年 04 月 09 日に読んだ。
目次
メモ
p5
こう書くと、コンパイルエラーというのは良くないもの避けるべきものと思えるかもしれません。
確かに、プログラムを書いてコンパイルエラーが発生したらそれを直す必要があり、手間がかかります。
とくに、 TypeScript を使わずに素のJavaScriptを使う人からは、
「JavaScript ならコンパイルエラーなんて出ないのに、わざわざ TypeScript を使ってコンパイルエラーを直すという仕事を増やす理由がわからない」という意見が聞かれることがあります。
しかし、実際にはそうではなく、コンパイルエラーはむしろ TypeScript 開発の頼もしい味方です。
どんなに優れた人でもまったく間違えずにプログラムを書くのは難しく、ミスが発生するのは当たり前です。
コンパイルエラーがあるほうがそのミスがすぐに発見される可能性が高くなり、ミスが残ってしまう(バグが発生する) ことが少なくなります。
これにより、成果物のクオリティが高まります。
これが静的型システム型安全性の恩恵です。
コンパイルエラーに関するこの考え方は、以下に引用する名言にまとまっています。
・コンパイルエラーは普通
・コンパイルエラーが出たらありがとう
・コンパイルエラーが出たら大喜び
(出典: 「江添亮のC++ 入門』 注5)
Typescriptの“独自機能”は避けるべき? p14
その一方で、 TypeScript には、JavaScriptにはない "独自機能" がいくつか存在します。
具体的には enum や namespace がこれに相当します。
これらの機能はランタイムの挙動を持ちますが、これらはJavaScriptには存在しません。
このように、 enum や namespace などの独自機能は現在の TypeScript の方針にそぐわないため、
現代的な TypeScript プログラミングでは使うべきではないという意見が存在します。
筆者の意見としても、 enum などの独自機能は使うべきではないと考えています。
そのため、本書ではこれらの機能は解説していません。
これらの機能を使うべきではない理由としては、これらが独自機能であるゆえに複雑過ぎるという点が挙げられます。
これらの構文は、普通のJavaScript の構文と比べて複雑な挙動をする傾向にあります。
その理由は利便性のためですが、複雑な挙動はプログラムの理解しにくさにつながるため筆者としては積極的な利用はお勧めできません。
幸い、本書で紹介する内容を理解すれば、これらの独自機能に頼る必要はありません。
既存の TypeScript プログラムを理解するために enumなどの学習が必要になることはあるかもしれませんが、
少なくとも新規のTypeScriptコードでこれらの機能を使う必要はないと考えても問題ないでしょう。
p16
ただし、1ヵ所だけデフォルトの package.json から書き換える必要があります。
具体的には、 "type": "module" を追加する必要があります。
適当な場所、たとえば "main"の後ろに追加してみましょう。
tsconfig.json の準備 p18
では、このtsconfig.jsonを本書の設定に合わせて書き換えていきます。
まず targetコンパイラプションを書き換えましょう。
"target": "es2016"
"target": "es2020"
次に、module コンパイラオプションも書き換えます。
"module": "commonjs"
"module": "esnext"
これはモジュールに関連する構文 (第7章)をどう取り扱うかを決めるコンパイラオプションです。
古いバージョンのNode.jsではCommonJS(第7章のコラム36) しか対応していないためトランスバイルが必要でしたが、
本書が採用する Node.jsバージョンではES Modules (→第7章のコラム35)が解釈できるため、
それに 合わせてオプションを変更します。
さらに、module Resolution コンパイラオプションをnodeにします。
// "moduleResolution":"node"
"moduleResolution": "node"
p22
サードパーティのツールを使いたくない方はtsc --watchもお勧めです。
このようにtscを watchモードで起動しておくと、.tsファイルが保存されるたびに自動的に再コンパイルを実行してくれます。
letを避けてプログラムを読む人の負担を減らそう p33
本節では、TypeScript における変数宣言の構文として、constとletの2種類を紹介しました。
すでに説明したとおり、この2つの違いは「変数への再代入が可能かどうか」です。
letは変数への再代入が可能である一方、const はできません。
また、let では宣言時に式による初期値の指定を省略することができましたが、 const ではこれはできません。
const を使用するときは必ずconst 変数名=式の形で変数に最初に入る値を指定しなければいけません。
そして、 const で宣言した変数は再代入できないので、最初に指定した変数の中身はそのあと変わりません。
ちなみに、 const というキーワードはプログラミング言語によっては「定数」を宣言するためのキーワードとして使われています。
TypeScript においても、一度値を決めたら中身が変わらない変数なのでこれを「定数」と呼んでもよさそうですね。
ただ、実際にはこれを「定数」と呼ぶ人はあまりいないようです。
const で宣言するものはあくまで「変数」であるとする見方が主流です。
そして、驚くべきことに、プログラムの種類や書き手の流派にもよりますが、
一般的なWebアプリケーションを TypeScript で書くならば、普通に書けば変数の9割以上は const で宣言されることになります。
let が変数宣言に 使われることは少ないのです。
その理由は、ほとんどの変数は一度代入すれば十分であり、再代入の必要がないからです。
これは実際にプログラムを書くようにならないと実感するのが難しいかもしれませんが、
典型的なプログラムにおいては変数は何らかの処理の結果に名前をつけて、再利用可能なように保存しておくという使い道がほとんどです。
違う処理の結果は違う変数に保存すればよいので、わざわざ同じ変数の値を変えるようなプログラムを書く必要がないのです。
このコラムで読者に伝えたいのは、極力 const を使って変数を宣言すべきであるということです。
できるだけ let の使用は避けてください。
絶対に let が必要だと断言できる場面でしか、 let を使うべきではありません。
その理由は、 let を使うとプログラムを読む人に負担がかかるからです。
業務でプログラムを書く場合、複数人で同じプログラムをいじることが多くあります。
そのため、ほかの人が読んでもわかりやすいプログラムを書くことが重要です。
const という機能があるにもかかわらずあえてletを使うということは、
プログラムを読む人に対して「この変数はあとで再代入されますよ」という意思表示をしていることになります。
これにより、そのプログラムを読む人は、いつその変数に再代入されるのか目を光らせながら読まなければいけません。
p53
=== を説明したので次に==の説明・・・・・・といきたいところですが、実は、==と!=は基本的に使うべきではありません。
==が必要な場面はまれであり、ほとんどの場合=== を使うほうが適しています。
その理由は===のほうがより厳密な一致判定を行ってくれるからです。
というのも、実は==は異なる型の間の比較を行った場合、暗黙の型変換を行ってから両者を比較するため true になることがあります。
p89
ほとんどの場合、 interface宣言はtype文で代用可能です。
しかもtype文のほうがより多くの場面で使えるので、interface 宣言は使用せずに type文のみを使うという流儀もあるようです(筆者もそうです)。
実際のところ、特定の場合を除いて type 文のみを使っておけば困ることはないでしょう。
そのため、これから TypeScript プログラムで開発を行う読者の方は interface宣言を使う機会が少ないかもしれません。
ただ、type 文が存在せず interface 宣言のみ利用可能だった時期があった (2014年以前) という歴史的経緯のために、
オブジェクトの型を作る際は interface宣言を使うという手癖がある人も多少いるようです。
p100
この節では型引数 (type parameters) に関する解説をします。
型引数は、型を定義するときにパラメータを持たせることができるというもので、ジェネリクス (4.4)に少し似ています。
型引数が使えるようになると型定義の幅が大きく広がります。
for-of 文によるループ p110
配列を扱う際に非常に便利なのが for-of 文です。
タプル型 p111
タプル型 (tuple types) とは、要素数が固定された配列型です。
タプル型は、要素数が固定されている代わりに、配列のそれぞれの要素に異なる型を与えることができるという特徴を持ちます。
p137
ちなみに、関数宣言は巻き上げ (hoisting) という特有の挙動を持ちます。
これは、関数宣言より前にその関数が使えるというものです。
p157
逆に「この関数はこの型の値を返すべきである」という真実を別に用意したいならば、返り値の型を明示すべきです。
基本的には返り値の型を明記したほうが有利なので、返り値の型は必ず書くという派閥もあります。
ジェネリクス p168
ジェネリクス (generics) とは、型引数を受け取る関数を作る機能のことです。
ジェネリクスはTypeScript プログラミングにおいて欠かすことができない機能のひとつです。
この本を前から順番に読んでいる方にとっては、型引数という概念はすでに馴染み深いものでしょう。
一方、ジェネリクスにおいては型引数を持つのは関数です。
そして、型引数に何が入るのかは関数を呼び出す際に決まります。
アロー関数における this p223
アロー関数 は this に関して特殊な性質を持ちます。
具体的には、アロー関数はthis を外側の関数から受け継ぎます。
これは、プログラムが現在のスコープだけでなくその外側のスコープに属する変数を使用できるのと似ています。
ですから、 アロー関数は自分自身のthis を持たないと言い換えることもできます。
自身のthis を持たないゆえに、 アロー関数内の this はその外側の関数における this と同じなのです。
finallyで脱出に割り込む p237
実は、try-catch 文にはまだ紹介していなかった機能があります。
それは finally ブロックです。
実は、try-catch 文の後ろにはさらに finally {} の形の finally ブロックを付け加えることができます (try-catch- finally 文)。
また、 catchブロックを省略して try-finally という形にすることもできます。
基本的には、 finally ブロックの内容はエラーが発生してもしなくても実行されます。
つまり、try ブロックを実行してもエラーが発生しなかった場合、次に finally ブロックが実行されて、その終了を以ってtry 文の処理完了となります。