なぜTypeScriptを使うのか?

TypeScriptの主要なゴールは次の2つです

  • JavaScriptに任意の型システムを追加する(型を利用するかどうかは開発者の自由です)

  • JavaScriptの将来のバージョンで計画されている機能を、現在の(モダンではない)JavaScript環境でも使えるようにする

では、なぜこれらのゴールが重要なのでしょうか?ここでは、これらのゴールを目指す動機について説明します。

TypeScriptの型システム

なぜJavaScriptに型を追加する必要があるのか?」と疑問に思うかもしれません。

型は、コードの品質と読みやすさを高めることが実証されています。大規模なチーム(Google、Microsoft、Facebook)は、常にこの結論に至っています。具体的には以下の通りです:

  • 型は、リファクタリングをする際の開発スピードを高めます。型があることによって、コードを書いている時点でエラーに気づくことができます。そして、すぐにエラーを修正できます。ランタイム(実行時)で、はじめてエラーに気づいて、コードに戻って修正するよりも、ずっと効率的です。開発中に早い段階でエラーに気づけるということは非常に素晴らしいことです。

  • 型は、それ自体が、完璧なドキュメントです。関数のシグネチャは定理であり、関数の本体は証明です。

しかし、型には必要以上に儀式的な面があります。そのため、TypeScriptは、型を導入する障壁を可能な限り低くしています。次にそれを説明します。

あなたの書いたJavaScriptはTypeScriptです

TypeScriptは、JavaScriptコードのコンパイル時の型安全性を提供します。その名前はこれを表しています。TypeScriptが素晴らしい点は、型を利用するかどうかは、完全に任意(オプション)である、ということです。例えば、何らかのJavaScriptコードが書かれた.jsファイルを、.tsファイルに名前を変更してコンパイルしたとしても、TypeScriptのコンパイラは、もとのJavaScriptファイルと同じ有効な.jsを出力します。TypeScriptは、意図的な、かつ厳密な、JavaScriptのスーパーセット(上位互換)であり、任意の型チェックの仕組みを持ったプログラミング言語です。

暗黙的な型推論(Type inference)

TypeScriptは、コーディングの生産性に対する影響をなるべく小さく抑えながら型の安全性を提供するために、可能な限り、型推論を行います。型推論とは、TypeScriptが、ソースコードを解析し、そのコードの流れから、変数や関数などの型を推測してくれる仕組みのことです。たとえば、次の例を見てください。この例では、TypeScriptはfooの変数の型がnumber(数値)であると推測します。そのため、2行目にエラーが表示されます。なぜなら、number(数値)string(文字列)を代入しているからです:

var foo = 123;
foo = '456'; // エラー: `string` を `number` に代入できません
// foo は number でしょうか? それとも string でしょうか?

型推論が必要とされることには、大きな理由があります。あるプロジェクトで、この例のようなコードを書いた場合、ほかの部分のコードでfooを扱うときに、それがnumberであるかstringであるかが分かりません。このような問題は、大規模なプロジェクトで、よく発生します。TypeScriptは型を推論することによって、このような不明確なコードに対して、エラーを表示してくれます。後で、TypeScriptが行う型推論のルールについて、くわしく説明します。

明示的な型指定(型アノテーション)

ここまでで述べたように、TypeScriptは、安全に行える場合は、できる限り、型を推論します。しかし、型推論の結果が正しくない場合、あるいは、正確でない場合は、開発者が、明示的にコード上で、型を指定する(型アノテーションを書く)ことができます。型アノテーションを書くことによって、以下のメリットを得ることができます:

  1. コンパイラの理解を助けるだけでなく、より、重要なことに、あなたが書いたコードを次に読まなくてはならない開発者にとってのドキュメントになります(それは、将来のあなたかもしれません!)。

  2. コンパイラがどのようにコードを理解するか、ということを強制します。つまり、コードに対する開発者の正しい理解を、コンパイラの型チェックのアルゴリズムに反映するということです。

TypeScriptは、ほかの任意な型付き言語(ActionScriptやF#など)で一般的な、末尾型アノテーションを使用します。つまり、この例のように、対象の後ろにコロンをつけて、型を指定します:

var foo: number = 123;

型が一致しない場合は、コンパイラはエラーを出力します。VSCodeを使っていれば、エディター上にエラーが表示されます:

var foo: number = '123'; // エラー: `string` を `number` に代入できません

TypeScriptがサポートしている型アノテーションについては、このあとのセクションで詳しく説明します。

構造的な型(Structural type system)

いくつかの言語(特に型付き言語と呼ばれるもの)において、静的な型付けは、必要以上に、儀式的なコードになってしまいます。なぜなら、コードが確実に正しく動作することが分かっている場合でも、構文ルールによって、同じようなコードをコピー&ペーストすることが強制されるからです。それは、たとえば、C#のautomapper for C#のようなものが必要になる理由です。TypeScriptは、JavaScript 開発者に対する認知的な負荷をなるべく小さくするため、構造的な型(structural type)を採用しています。これが意味することは、ダックタイピング(duck typing)が第一級(言語レベルでサポートされている)のものである、ということです。これを理解するため、次の例を見てみましょう。この関数iTakePoint2Dは、必要なプロパティ(この例ではxy)を含むオブジェクトであれば、何でも引数として受け入れます:

interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
var point2D: Point2D = { x: 0, y: 10 }
var point3D: Point3D = { x: 0, y: 10, z: 20 }
function iTakePoint2D(point: Point2D) { /* なんらかの処理 */ }
iTakePoint2D(point2D); // 全く同じ構造なので問題なし
iTakePoint2D(point3D); // 追加のプロパティがあっても問題なし
iTakePoint2D({ x: 0 }); // エラー: `y` が存在しない

型チェックでエラーがあってもJavaScriptは出力される

JavaScriptのコードをTypeScriptに移行することを簡単にするため、デフォルトの設定では、コンパイルエラーがあったとしても、TypeScriptは有効なJavaScriptを出力します。例えば、次のコードを見てください。このコードにはエラーがあります。これをコンパイルすると:

var foo = 123;
foo = '456'; // エラー: `string` を `number` に代入できません

以下のJavaScriptが出力されます:

var foo = 123;
foo = '456';

これによって、JavaScriptのコードを少しずつTypeScriptに移行することができます。これは他の言語のコンパイラとは全く異なる動作です。そして、これが、TypeScriptに移行する理由の1つです。

アンビエント宣言(declare)によって、既存のJavaScriptライブラリでも型を利用できる

TypeScriptの設計上の大きなゴールは、既存のJavaScriptライブラリであっても、安全かつ簡単に利用できるようにすることです。TypeScriptはこれを型宣言(declare)で行います。TypeScriptにおいて、型宣言にどれくらいの労力をかけるかは自由です。より多くの労力をかければ、より多くの型安全性と、IDEでのコード補完が得られます。ほとんど全ての有名なJavaScriptライブラリの型定義は、DefinitelyTyped コミュニティによって既に作成されています。そのため、

  1. 型宣言ファイル(アンビエント宣言が書かれたファイル)が既に存在します。

  2. あるいは、最低でも、きちんとレビューされた型宣言のテンプレートがすでに利用可能です。

独自の型宣言ファイルを作成する簡単な例として、jqueryの簡単な例を考えてみましょう。TypeScriptは、デフォルトの設定では、(望ましいJavaScriptコードのように)、変数を使う前に宣言する(つまり、どこかでvarを書く)ことを期待しています。

$('.awesome').show(); // エラー: `$` が存在しません。

簡単な修正方法は、declareを使って、グローバル変数$が、実行時に必ず存在することをTypeScriptに伝えることです。

declare var $: any;
$('.awesome').show(); // 問題なし!

必要に応じて、より詳細に型を定義することによって、プログラミングのエラーを防ぐことができます。

declare var $: {
(selector: string): any;
};
$('.awesome').show(); // 問題なし!
$(123).show(); // エラー: selector は string でなければなりません

TypeScriptの基本を理解した後で、既存のJavaScriptコードの型宣言を追加する方法について、詳しく説明します(interfaceanyなど)。

モダンなJavaScriptの機能を今すぐに利用できる

TypeScriptは、古いJavaScript(ES5以前)の実行環境でも、ES6以降のバージョンで計画されている多くの機能を使えるようにしています。TypeScriptチームは積極的に機能を追加しています。機能は今後もどんどん増えていく予定です。これらについては独自のセクションで説明します。例えば、クラス構文(ES6で追加された機能)もその1つです:

class Point {
constructor(public x: number, public y: number) {
}
add(point: Point) {
return new Point(this.x + point.x, this.y + point.y);
}
}
var p1 = new Point(0, 10);
var p2 = new Point(10, 20);
var p3 = p1.add(p2); // { x: 10, y: 30 }

かわいい太っちょのアロー関数もその1つです:

var inc = x => x+1;

まとめ

このセクションでは、TypeScriptを使う理由と、TypeScriptが目指しているゴールを説明しました。では、これから、TypeScriptが提供している機能や利便性について詳しく見ていきましょう。