JavaScriptのすべての値は、異なる操作を実行することで観察できる一連の動作を持っています。これは抽象的に聞こえますが、簡単な例として、message
という名前の変数に対して実行する可能性のあるいくつかの操作を考えてみましょう。
js
// Accessing the property 'toLowerCase'// on 'message' and then calling itmessage.toLowerCase();// Calling 'message'message();
これを分解すると、実行可能な最初のコード行はtoLowerCase
という名前のプロパティにアクセスし、それを呼び出します。2番目のコードはmessage
を直接呼び出そうとします。
しかし、message
の値を知らないと仮定すると(これはよくあることですが)、このコードの実行を試みたときにどのような結果が得られるかを確実に言うことはできません。各操作の動作は、最初に持っていた値に完全に依存します。
message
は呼び出し可能ですか?toLowerCase
というプロパティを持っていますか?- もしそうなら、
toLowerCase
は呼び出し可能ですか? - これらの両方の値が呼び出し可能な場合、それらは何を返しますか?
これらの質問への答えは、通常、JavaScriptを書く際に頭の中に留めておくものであり、すべての詳細が正しいことを願う必要があります。
message
が次のように定義されていたとしましょう。
js
const message = "Hello World!";
おそらく推測できると思いますが、message.toLowerCase()
を実行しようとすると、同じ文字列が小文字で返されます。
2行目のコードはどうでしょうか?JavaScriptに詳しい方は、これが例外で失敗することをご存知でしょう。
txt
TypeError: message is not a function
このような間違いを避けられると素晴らしいですね。
コードを実行するとき、JavaScriptランタイムが何をするかを選択する方法は、値の型(どのような動作と能力を持っているか)を把握することです。それがTypeError
が暗示していることの一部です。つまり、文字列"Hello World!"
は関数として呼び出すことはできません。
プリミティブなstring
やnumber
などの一部の値については、typeof
演算子を使用して実行時に型を識別できます。しかし、関数のような他のものについては、型を識別するための対応するランタイムメカニズムはありません。たとえば、次の関数を考えてみましょう。
js
function fn(x) {return x.flip();}
この関数は、呼び出し可能なflip
プロパティを持つオブジェクトが与えられた場合にのみ機能することがコードを読めば観察できますが、JavaScriptはコードの実行中に確認できる方法でこの情報を公開していません。純粋なJavaScriptでfn
が特定の値に対して何をするかを判断する唯一の方法は、それを呼び出して何が起こるかを確認することです。この種の動作は、コードを実行する前にコードが何をするかを予測することを困難にし、つまり、コードを書いているときにコードが何をするかを把握することがより困難になることを意味します。
このように見ると、型とは、fn
に渡すことができる値と、クラッシュする値を記述する概念です。JavaScriptは、何が起こるかを確認するためにコードを実行するという、動的型付けのみを実際に提供します。
代替手段は、コードの実行を開始する前に、コードが何をするかを予測するために静的型システムを使用することです。
静的型チェック
以前、関数としてstring
を呼び出そうとしたときに発生したTypeError
を思い出してください。ほとんどの人は、コードを実行したときにエラーが発生することを好みません。それらはバグと見なされます。そして、新しいコードを書くときは、新しいバグを導入しないように最善を尽くします。
少しだけコードを追加し、ファイルを保存し、コードを再実行してすぐにエラーが表示された場合、問題を迅速に特定できるかもしれません。しかし、常にそうとは限りません。機能を十分にテストしていなかったため、発生する可能性のあるエラーに実際には遭遇しない可能性があります。あるいは、幸運にもエラーを目撃した場合、大規模なリファクタリングを行い、掘り下げて確認しなければならない多くの異なるコードを追加してしまったかもしれません。
理想的には、コードを実行する前にこれらのバグを見つけるのに役立つツールがあればよいでしょう。それが、TypeScriptのような静的型チェッカーが行うことです。静的型システムは、プログラムを実行したときに値がどうなるかの形状と動作を記述します。TypeScriptのような型チェッカーは、その情報を使用して、何かがおかしくなりそうな場合に教えてくれます。
tsTry
constmessage = "hello!";This expression is not callable. Type 'String' has no call signatures.2349This expression is not callable. Type 'String' has no call signatures.(); message
TypeScriptで最後のサンプルを実行すると、最初にコードを実行する前にエラーメッセージが表示されます。
例外以外の失敗
これまで、ランタイムエラーのような特定の事柄について議論してきました。これは、JavaScriptランタイムが何かがおかしいと考えていることを示すケースです。これらのケースは、ECMAScript仕様に、予期しない事態が発生した場合に言語がどのように動作すべきかについての明示的な指示があるために発生します。
たとえば、仕様では、呼び出し可能でないものを呼び出そうとするとエラーが発生すると規定されています。これは「明白な動作」のように聞こえるかもしれませんが、オブジェクトに存在しないプロパティにアクセスした場合もエラーが発生するはずだと想像できるでしょう。代わりに、JavaScriptは異なる動作をし、値undefined
を返します。
js
const user = {name: "Daniel",age: 26,};user.location; // returns undefined
最終的に、静的型システムは、すぐにエラーをスローしない「有効な」JavaScriptであっても、システム内でエラーとしてフラグを立てるコードを判断する必要があります。TypeScriptでは、次のコードはlocation
が定義されていないというエラーを生成します。
tsTry
constuser = {name : "Daniel",age : 26,};Property 'location' does not exist on type '{ name: string; age: number; }'.2339Property 'location' does not exist on type '{ name: string; age: number; }'.user .; location
場合によっては、表現できるものにトレードオフが生じますが、その意図はプログラム内の正当なバグを捕捉することです。そして、TypeScriptは多くの正当なバグを捕捉します。
例:タイプミス、
tsTry
constannouncement = "Hello World!";// How quickly can you spot the typos?announcement .toLocaleLowercase ();announcement .toLocalLowerCase ();// We probably meant to write this...announcement .toLocaleLowerCase ();
呼び出されていない関数、
tsTry
functionflipCoin () {// Meant to be Math.random()returnOperator '<' cannot be applied to types '() => number' and 'number'.2365Operator '<' cannot be applied to types '() => number' and 'number'.Math .random < 0.5;}
または基本的なロジックエラー。
tsTry
constvalue =Math .random () < 0.5 ? "a" : "b";if (value !== "a") {// ...} else if (This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.2367This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.value === "b") {// Oops, unreachable}
ツール用の型
TypeScriptは、コードでミスをしたときにバグを捕捉できます。それは素晴らしいことですが、TypeScriptはさらに、最初にそれらのミスを犯すのを防ぐことができます。
型チェッカーには、変数やその他のプロパティで正しいプロパティにアクセスしているかどうかなどを確認するための情報があります。その情報があれば、使用したいプロパティを提案することもできます。
つまり、TypeScriptはコード編集にも活用でき、コア型チェッカーはエディターに入力中にエラーメッセージとコード補完を提供できます。これは、TypeScriptでのツールについて話すときによく言及されることの一部です。
tsTry
importexpress from "express";constapp =express ();app .get ("/", function (req ,res ) {res .sen });app .listen (3000);
TypeScriptはツールを真剣に考えており、これは入力中の補完やエラーだけにとどまりません。TypeScriptをサポートするエディターは、エラーを自動的に修正する「クイックフィックス」、コードを簡単に再編成するためのリファクタリング、変数定義にジャンプしたり、特定の変数へのすべての参照を検索したりするための便利なナビゲーション機能を提供できます。これらはすべて型チェッカーに基づいて構築されており、完全にクロスプラットフォームであるため、お気に入りのエディターでTypeScriptのサポートが利用できる可能性が高いです。
tsc
、TypeScriptコンパイラー
型チェックについて話してきましたが、まだ型チェッカーを使用していません。新しい仲間であるtsc
、TypeScriptコンパイラーを紹介しましょう。まず、npm経由で入手する必要があります。
sh
npm install -g typescript
これにより、TypeScriptコンパイラー
tsc
がグローバルにインストールされます。ローカルのnode_modules
パッケージからtsc
を実行する場合は、npx
または同様のツールを使用できます。
次に、空のフォルダーに移動して、最初のTypeScriptプログラムhello.ts
を作成してみましょう。
tsTry
// Greets the world.console .log ("Hello world!");
ここでは飾り気がないことに注意してください。この「ハローワールド」プログラムは、JavaScriptで「ハローワールド」プログラムを作成する場合と同じように見えます。そして、typescript
パッケージによってインストールされたコマンドtsc
を実行して型チェックしてみましょう。
sh
tsc hello.ts
ジャーン!
ちょっと待ってください。「ジャーン」とは一体何ですか? tsc
を実行しましたが、何も起こりませんでした!型エラーはなかったため、報告するものがなかったため、コンソールには何も出力されませんでした。
ただし、もう一度確認してください。代わりに、ファイル出力が得られました。現在のディレクトリを見ると、hello.ts
の隣にhello.js
ファイルが表示されます。これは、tsc
がプレーンなJavaScriptファイルにコンパイルまたは変換した後のhello.ts
ファイルからの出力です。そして、内容を確認すると、TypeScriptが.ts
ファイルを処理した後に吐き出すものがわかります。
js
// Greets the world.console.log("Hello world!");
この場合、TypeScriptが変換するものはほとんどなかったため、私たちが書いたものと同じように見えます。コンパイラーは、人が書くようなクリーンで読みやすいコードを生成しようとします。それは常に簡単ではありませんが、TypeScriptは一貫してインデントし、コードが異なるコード行にまたがっている場合に注意し、コメントを保持しようとします。
もし型チェックエラーを導入した場合どうでしょうか? hello.ts
を書き直してみましょう。
tsTry
// This is an industrial-grade general-purpose greeter function:functiongreet (person ,date ) {console .log (`Hello ${person }, today is ${date }!`);}greet ("Brendan");
tsc hello.ts
を再度実行すると、コマンドラインにエラーが表示されることに注意してください!
txt
Expected 2 arguments, but got 1.
TypeScriptは、greet
関数に引数を渡すのを忘れたことを教えてくれます。当然のことです。これまで標準のJavaScriptしか記述していませんが、それでも型チェックによってコードの問題を見つけることができました。ありがとう、TypeScript!
エラー付きのエミット
最後の例で気づかなかったことが1つあります。hello.js
ファイルが再び変更されたことです。そのファイルを開くと、内容はまだ基本的に入力ファイルと同じように見えることがわかります。これは、tsc
がコードに関するエラーを報告したという事実を考えると、少し驚きかもしれませんが、これはTypeScriptのコアバリューの1つに基づいています。多くの場合、あなたはTypeScriptよりもよく知っているでしょう。
以前から繰り返しているように、コードの型チェックは実行できるプログラムの種類を制限するため、型チェッカーが許容できるものにはトレードオフがあります。ほとんどの場合、それは問題ありませんが、それらのチェックが邪魔になるシナリオもあります。たとえば、JavaScriptコードをTypeScriptに移行し、型チェックエラーを導入していると想像してください。最終的には型チェッカーのために物事を整理することになるでしょうが、元のJavaScriptコードはすでに動作していました!TypeScriptに変換することで、実行が妨げられるべきでしょうか?
そのため、TypeScriptはあなたの邪魔をしません。もちろん、時間が経つにつれて、ミスに対してもう少し防御的になり、TypeScriptをより厳密に動作させたいと思うかもしれません。その場合は、noEmitOnError
コンパイラーオプションを使用できます。hello.ts
ファイルを変更して、そのフラグ付きでtsc
を実行してみてください。
sh
tsc --noEmitOnError hello.ts
hello.js
が更新されないことに気づくでしょう。
明示的な型
これまで、TypeScriptにperson
やdate
が何であるかを伝えていませんでした。コードを編集して、person
がstring
であり、date
がDate
オブジェクトであるべきであることをTypeScriptに伝えてみましょう。また、date
でtoDateString()
メソッドを使用します。
tsTry
functiongreet (person : string,date :Date ) {console .log (`Hello ${person }, today is ${date .toDateString ()}!`);}
私たちが行ったのは、greet
が呼び出し可能な値の型を記述するために、person
とdate
に型注釈を追加したことです。そのシグネチャは「greet
は、型がstring
のperson
と型がDate
のdate
を受け取る」と読むことができます。
これにより、TypeScriptはgreet
が誤って呼び出された可能性のある他のケースについて教えてくれます。例えば…
tsTry
functiongreet (person : string,date :Date ) {console .log (`Hello ${person }, today is ${date .toDateString ()}!`);}Argument of type 'string' is not assignable to parameter of type 'Date'.2345Argument of type 'string' is not assignable to parameter of type 'Date'.greet ("Maddison",Date ());
あれ? TypeScript が2番目の引数でエラーを報告していますが、なぜでしょう?
少し驚くかもしれませんが、JavaScriptでDate()
を呼び出すと、string
が返されます。一方、new Date()
でDate
を構築すると、期待通りのものが得られます。
とにかく、エラーはすぐに修正できます。
tsTry
functiongreet (person : string,date :Date ) {console .log (`Hello ${person }, today is ${date .toDateString ()}!`);}greet ("Maddison", newDate ());
明示的な型注釈を常に書く必要はないということを覚えておいてください。多くの場合、TypeScriptは型を省略しても、型を*推論*(または「把握」)することができます。
tsTry
letmsg = "hello there!";
msg
がstring
型であることをTypeScriptに伝えなくても、それを把握できました。これは便利な機能であり、型システムが結局同じ型を推論する場合は、注釈を追加しないのが最善です。
注:前のコード例のメッセージバブルは、エディターで単語の上にカーソルを合わせたときに表示されるものです。
型消去
上記の関数greet
をtsc
でコンパイルしてJavaScriptを出力するとどうなるか見てみましょう。
tsTry
"use strict";function greet(person, date) {console.log("Hello ".concat(person, ", today is ").concat(date.toDateString(), "!"));}greet("Maddison", new Date());
ここで2つの点に注意してください。
person
とdate
のパラメータには、型注釈がなくなりました。- バッククォート(
`
文字)を使用した文字列である「テンプレート文字列」は、連結を伴うプレーンな文字列に変換されました。
2番目のポイントについては後で詳しく説明しますが、まずは最初のポイントに焦点を当てましょう。型注釈はJavaScript(厳密に言えばECMAScript)の一部ではないため、TypeScriptを未修正で実行できるブラウザやランタイムは実際には存在しません。これが、そもそもTypeScriptにコンパイラが必要な理由です。実行できるように、TypeScript固有のコードを削除または変換する方法が必要なのです。ほとんどのTypeScript固有のコードは消去され、同様に、ここでは型注釈が完全に消去されました。
覚えておいてください:型注釈は、プログラムの実行時の動作を絶対に変化させません。
ダウンレベル
上記のもう一つの違いは、テンプレート文字列が次のように書き換えられたことです。
js
`Hello ${person}, today is ${date.toDateString()}!`;
から
js
"Hello ".concat(person, ", today is ").concat(date.toDateString(), "!");
なぜこれが起こったのでしょうか?
テンプレート文字列は、ECMAScript 2015(別名ECMAScript 6、ES2015、ES6など - *聞かないでください*)というバージョンのECMAScriptの機能です。TypeScriptには、新しいバージョンのECMAScriptから、ECMAScript 3やECMAScript 5(別名ES3およびES5)などの古いバージョンにコードを書き換える機能があります。新しい、または「上位」バージョンのECMAScriptから、古い、または「下位」バージョンに移行するこのプロセスは、ダウンレベルと呼ばれることがあります。
デフォルトでは、TypeScriptはECMAScriptの非常に古いバージョンであるES3をターゲットにしています。target
オプションを使用すると、もう少し新しいものを選択できます。--target es2015
を指定して実行すると、TypeScriptはECMAScript 2015をターゲットにするように変更され、つまり、コードはECMAScript 2015がサポートされている場所ならどこでも実行できるようになります。したがって、tsc --target es2015 hello.ts
を実行すると、次の出力が得られます。
js
function greet(person, date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);}greet("Maddison", new Date());
デフォルトのターゲットはES3ですが、現在のブラウザの大部分はES2015をサポートしています。したがって、一部の古いブラウザとの互換性が重要でない限り、ほとんどの開発者はターゲットとしてES2015以上を安全に指定できます。
厳密性
ユーザーによって、TypeScriptに求めるものは異なります。プログラムの一部のみを検証し、優れたツールを使用できるような、より緩やかなオプトインエクスペリエンスを求める人もいます。これがTypeScriptのデフォルトのエクスペリエンスであり、型はオプションであり、推論は最も寛容な型を取り、潜在的なnull
/undefined
値のチェックは行われません。tsc
がエラーに直面しても発行するのと同じように、これらのデフォルトは邪魔にならないように設定されています。既存のJavaScriptを移行する場合、これは望ましい最初のステップかもしれません。
対照的に、多くのユーザーはTypeScriptができるだけすぐに検証することを好むため、言語には厳密性の設定も用意されています。これらの厳密性の設定により、静的型チェックがスイッチ(コードがチェックされるかされないかのどちらか)からダイヤルに近いものに変わります。このダイヤルを回せば回すほど、TypeScriptはより多くのチェックを行います。これには少し余分な作業が必要になる場合がありますが、一般的に、長期的にはそれに見合う価値があり、より徹底的なチェックとより正確なツールが可能になります。可能な限り、新しいコードベースでは常にこれらの厳密性のチェックをオンにする必要があります。
TypeScriptには、オンまたはオフにできるいくつかの型チェックの厳密性フラグがあり、特に明記されていない限り、すべての例はそれらをすべて有効にした状態で記述されます。CLIのstrict
フラグ、またはtsconfig.json
の"strict": true
は、それらをすべて同時にオンにしますが、個別にオフにすることもできます。知っておくべき2つの最も大きなものは、noImplicitAny
とstrictNullChecks
です。
noImplicitAny
TypeScriptは、一部の場所では型を推論しようとせず、代わりに最も寛容な型であるany
にフォールバックすることを思い出してください。これは最悪の事態ではありません。結局のところ、any
にフォールバックするのは、まさにプレーンなJavaScriptのエクスペリエンスです。
ただし、any
を使用すると、そもそもTypeScriptを使用する目的が損なわれることがよくあります。プログラムの型付けが多いほど、検証とツールがより多くなり、コードを作成する際にバグが発生する回数が少なくなります。noImplicitAny
フラグをオンにすると、型が暗黙的にany
として推論される変数でエラーが発行されます。
strictNullChecks
デフォルトでは、null
やundefined
のような値は、他の任意の型に割り当てることができます。これにより、一部のコードを簡単に記述できますが、null
やundefined
の処理を忘れると、世界中で数え切れないほどのバグが発生します。中には、それを10億ドルの間違いと考える人もいます。strictNullChecks
フラグを使用すると、null
とundefined
の処理がより明示的になり、null
とundefined
の処理を*忘れたか*どうかを心配する必要がなくなります。