こんにちは、かつコーチです。
前回はtscコマンドとtsconfig.jsonを使った環境構築をやりました。
今回は、実際に同じ処理をJavaScriptとTypeScriptの両方で書いて、見た目や動きの違いを具体的に比べてみます。
「型があると具体的に何が嬉しいのか」を、コードレベルで実感してもらうのが今回のゴールです。
お題:会員情報を扱う簡単な処理
共通のお題
今回は「会員情報を受け取って、あいさつ文を作る」というシンプルな処理を題材にします。
- 名前(文字列)
- 年齢(数値)
- 会員かどうか(真偽値)
この3つの情報を受け取って、あいさつ文を返す関数を、JavaScriptとTypeScriptそれぞれで書いてみましょう。
JavaScriptで書く場合
素直に書いた実装
まずは、これまでのJavaScript基礎編と同じ感覚で書いてみます。
// JavaScript版
function createGreeting(name, age, isMember) {
const status = isMember ? "会員" : "非会員";
return name + "さん(" + age + "歳・" + status + ")、こんにちは!";
}
console.log(createGreeting("かつコーチ", 35, true));
// こんにちは、かつコーチさん(35歳・会員)、こんにちは!
見た目はシンプルで、動作にも問題はありません。
ただし、この関数には「何を渡せばいいか」という情報がコードのどこにも書かれていません。
引数の名前から推測はできますが、ageに文字列を渡してもエラーにはならず、そのまま実行されてしまいます。
// うっかり年齢を文字列で渡してしまっても...
console.log(createGreeting("たなか", "35歳", false));
// たなかさん(35歳歳・非会員)、こんにちは! ← 「歳歳」とおかしな文字列になるが、エラーにはならない
「歳歳」という不自然な文字列が出力されていますが、JavaScriptはこれをエラーとして扱いません。
実行はできてしまうため、画面を実際に見るまで間違いに気づけないのが問題です。
TypeScriptで書く場合
型注釈を付けた実装
同じ処理を、型注釈を付けてTypeScriptで書き直してみます。
// TypeScript版
function createGreeting(name: string, age: number, isMember: boolean): string {
const status: string = isMember ? "会員" : "非会員";
return name + "さん(" + age + "歳・" + status + ")、こんにちは!";
}
console.log(createGreeting("かつコーチ", 35, true));
// かつコーチさん(35歳・会員)、こんにちは!
name: string、age: number、isMember: booleanという部分が型注釈です。
「この引数にはこの種類の値しか渡せません」という約束を、関数の定義そのものに書き込んでいます。
型に合わない値を渡すとどうなるか
先ほどJavaScriptで問題になった「年齢に文字列を渡す」パターンを、TypeScriptで試してみましょう。
console.log(createGreeting("たなか", "35歳", false));
// エディタ上ですぐにエラー表示:
// Argument of type 'string' is not assignable to parameter of type 'number'.
コードを実行するまでもなく、エディタが赤い波線でその場でエラーを教えてくれます。
「number型が期待されている場所にstring型を渡そうとしています」という、間違いの原因がそのままメッセージに表れているのも大きなポイントです。
このコードは、コンパイルの段階でエラーになるため、そもそも実行されることすらありません。
Before/Afterで見る「型があることの安心感」
戻り値の勘違いに気づけるかどうか
もう一つ、私が実務でよく見かける勘違いのパターンを比べてみます。
❌ Before:JavaScriptで戻り値の型を勘違いしたまま使う
function getUserAge(user) {
return user.age; // 数値が返ってくるつもりで書いている
}
function isAdult(user) {
// getUserAgeが実は文字列 "20" のような形式で返ってきていたら…
return getUserAge(user) >= 18; // 文字列と数値の比較は動くこともあり、気づきにくい
}
getUserAgeが本当に数値を返しているかどうかは、実際に中身を確認しないと分かりません。
もしAPIの都合などで"20"という文字列が返ってきていた場合でも、比較演算子は動いてしまうことがあり、バグに気づくのが遅れます。
✅ After:TypeScriptで戻り値の型を明示する
function getUserAge(user: { age: number }): number {
return user.age;
}
function isAdult(user: { age: number }): boolean {
return getUserAge(user) >= 18;
}
getUserAgeの戻り値に: numberと明示しておくことで、もし内部の実装を誰かが誤って文字列を返すように変更してしまった場合、その時点でエディタがエラーを出してくれます。
「戻り値は必ず数値である」という約束が、コードの中に固定されるイメージです。
かつコーチが実際につまずいた話
私がJavaScriptだけで開発していた頃、あるAPIから返ってくる「在庫数」というデータを、てっきり数値だと思い込んでif (stock > 0)という条件分岐を書いていたことがあります。
ところが実際にはAPIの仕様上、在庫数は"0"のような文字列で返ってくる仕様でした。
"0"という空でない文字列は、JavaScriptの条件式ではtrueとして扱われるため、在庫がないはずなのに「在庫あり」の表示が出てしまうバグを引き起こしてしまいました。
これをTypeScriptで型を明示しながら書いていれば、stockの型をstringと定義した時点で「これは数値として扱えない」と自分自身が気づけたはずで、実際にTypeScriptに移行してからは同じ種類のミスがほぼなくなりました。
型を書く一手間は、未来の自分やチームメンバーを助けるための投資だと今では感じています。
まとめ
この記事のポイント
- 同じ処理でも、TypeScriptでは引数や戻り値に型注釈を付けて書く
- JavaScriptでは型が合わない値を渡してもエラーにならず、実行するまで気づけない
- TypeScriptでは型が合わない場合、コンパイル前のエディタ上でエラーが表示される
- 「戻り値の型」を明示しておくと、実装が変わったときの事故も未然に防げる
次に読むべき記事
型があることの安心感が伝わったところで、次回からは実際の型注釈の書き方を一つずつ学んでいきます。
まずは基本となるstring・number・booleanから見ていきましょう。
→ 次の記事:基本の型注釈:string・number・booleanの書き方