TypeScriptの「型」とはなにか

TypeScriptは、JavaScriptをベースにさまざまな機能を追加したプログラミング言語ですが、中でも特徴的なのは、名前にもある「型(Type)」という特徴です。

例えばJavaScriptの次のプログラムを見てみましょう。

let sum = 1 + 2;
console.log(sum); // 3

これをTypeScriptにした場合は、次のように「型」を宣言しなければなりません。

let sum: number = 1 + 2;
console.log(sum); // 3

なぜ、このような「型」を宣言しなければならないのでしょうか? どんなメリットがあるのでしょうか? ここでは、そんな「型」について紹介していきましょう。

型のメリット

まずは「型」があると、どんな良いことがあるのでしょう。次のJavaScriptのプログラムを見てみましょう。

let price;
price = 1000;
console.log(price * 1.1); // 1100

1000に1.1をかけ算した結果を表示しています。しかし、例えば開発をしている途中で、次のようなおかしな値を代入してしまったとしましょう。

let price;
price = 1000;
// ...
price = 'abc';
console.log(price * 1.1); // NaN

「abc」という「文字列」を代入してしまったため、計算ができなくなってしまい、結果は「NaN(Not a Number)」というエラーになってしまいました。

エラーになればまだ良いのですが、次のようなケースはもっとやっかいです。

let price, price2;
price = 1000;
price2 = '100';
price = price + price2; // 1000100になる
console.log(price * 1.1); // 1100110

priceとprice2を足して合計を出そうとしたら、うっかりprice2が文字列であったために文字列連結となって、さらにこれが計算の時には数字に変わってしまうため、結果は1100110という全く違う計算結果が出てきてしまいます。

このバグは非常に見つけにくく、この誤った計算結果を基に処理を続けようとしてしまうため、決済処理などの場合には非常に危険です。

JavaScriptは、このような柔軟性の代わりに非常に危うい言語設計になっています。

TypeScriptで型を宣言した場合、これらのプログラムはすべてエラーとなってコンパイルができません。

let price: number, price2: number;
price = 1000;
price2 = '100';
price = price + price2; // 1000100になる
console.log(price * 1.1); // 1100110

price2のところで「型 ‘string’ を型 ‘number’ に割り当てることはできません。」とエラーになりますし、price2をstringにした場合も足し算ができなくなります。こうして、プログラムの間違いを開発中に気がつくことができます。

静的型付けと動的型付け

なお、正確なことをいえばJavaScriptに変数の「型」がないわけではありません。プログラミング言語には、基本的には必ず「型」があります。ただし、JavaScriptは「動的型付け」というしくみで動作していて、変数の型が「その場で」決まります。

それに対して、TypeScriptでは「静的型付け」というあらかじめ型を宣言してから利用する方式に変更しています。

動的型付けの場合、非常に手軽にプログラムを作成することができます。あらかじめどんな値が入るのかを想定する必要もないですし、変数を自由に再利用することができます。しかし、大規模なプログラム開発だったり、チーム開発をすると、変数がいろいろな使われ方をされてしまっていると、どんな動きをするのかの見通しが悪くなったりして、見にくいプログラムになったり、バグが発生する原因になったりします。

そこで、TypeScriptでは静的型付けの概念を取り入れ、これらの問題を解消したというわけです。

型の変換

TypeScriptでも先のプログラムを正しく動作させることができます。それが「型変換」という手法です。次のように変更してみましょう。

let price: number = 1000;
let price2: number = Number('100');
price = price + price2;
console.log(price * 1.1);

文字列を代入するときに「Number()」で囲みます。これで、型が変換されてnumberとして処理されます。

型推論

TypeScriptは、型を常に宣言しなければならずに面倒と感じるかも知れませんが、実は型の宣言は省略できる場合があります。それは、変数を宣言するときに同時に代入した場合。次の例を見てみましょう。

let price = 1000;
let price2 = '100';
price = price + price2;
console.log(price * 1.1);

この場合、特に型は宣言していませんが、エラーが表示されます。

これは、TypeScriptが自動的に最初に代入された値の内容から型を「推論」しているため。実際、VSCodeで変数名にマウスカーソルを重ねると、正しい型が表示されています。

そのため、直接代入する変数などの場合には型の定義を省略することができるのです。

危険なany型

ではTypeScriptでは、一度型を決めた変数は、後から別のものを代入することはできないのでしょうか? 一応、やり方がいくつか準備されています。

まずは、型の定義を複数指定することができます。「|」で区切って指定します。

let age: number | string;
age = 25;
age = '25歳';
console.log(age);

この場合、「age」という変数には数字と文字列を代入することができるようになります。また、「なんでも入れられる」という「any」という型もあります。

let age: any;
age = 25;
age = '25歳';
console.log(age);

なお、実は代入をしない変数宣言時に型を指定しなかった場合は、「any」になります。

let age;
age = 25;
age = '25歳';
console.log(age);

こうすれば、従来通りのJavaScriptと同じようにプログラムを作ることもできます。

とはいえ、これは主に過去に開発したJavaScriptのプログラムを移植する際に、正しく行こうができないときのために準備されている手段であり、「any」を多用したプログラム開発はTypeScriptのメリットがなくなってしまうので、あまり良い方法とは言えません。

できればやはり、きちんと型を考えてプログラムを開発した方が良いでしょう。