くらしのマーケット開発ブログ

猫のいる会社、みんなのマーケットの技術ブログ

TypeScriptをプロダクトで使って便利だった話

はじめに

みんなのマーケットでwebエンジニアとして働いている高橋です。

今回は弊社で採用しているTypeScriptについて簡単に紹介します。みんなのマーケットではNode.jsおよびAngular等のフロントのjsに対して、TypeScriptを全面的に用いています。本記事では実際にプロダクトでTypeScriptを使用してメリットと感じた点をいくつか挙げていきます。

TypeScriptとは

TypeScriptはJavaScriptにコンパイルすることができる、静的型付けプログラミング言語です。変換後のjsには、型情報は残りません。TypeScriptを用いることで、動的型付け言語であるJavaScriptに対して、実行前に静的型チェックを行うことができます。また、型定義機能以外にも、classやmodule機能の拡張なども行われています(こちらの日本語資料が詳しいです)。これだけでも十分便利なのですが、おまけ機能として、ES2015等からES5への変換も行うことができます。

メリット1: 変数・引数・戻り値等の型を明示できる

以下、簡単なコードを例にして、TypeScriptのメリットを紹介していきます。手元にTypeScriptの実行環境が無い方は、Microsoftが公開している↓のプレイグラウンドで、TypeScriptのコンパイルを試してみることができます。(Runを押すと結果を確認することができます。)

https://www.typescriptlang.org/play/index.html#src=function%20stringToNumber(s%3A%20string)%3A%20number%20%7B%0D%0A%20%20%20%20return%20parseInt(s%2C%2010)%3B%0D%0A%7D%0D%0A%0D%0Alet%20a%20%3D%20stringToNumber(1)%3B%0D%0Aalert(a)%3B%0D%0A

f:id:curama-tech:20180222115335p:plain

function stringToNumber(s: string): number {
    return parseInt(s, 10);
}

let a = stringToNumber(1); // 数値を渡しているためここでエラーが出る!
alert(a);

JavaScriptとTypeScriptの大きな違いとして、変数や関数に型定義を記述できる点があります。上記のコードでの: string: numberという記述がそれにあたります。TypeScriptのコンパイル時に、コンパイラは型チェックを行い、型の不整合があった場合にはエラーを発生させます。

例えば上記のコードにおいて、stringToNumber関数は引数に"100"のような文字列が来ることを想定しています。しかし、プレイグラウンド上でstringToNumber(1)の部分に赤線が引かれてある部分を見ていただければわかるように、ここでは型エラーが発生しています。なぜならば、この関数の引数として許容されるのはstring型であり、number型ではないからです。このように、動作前に型チェックを行うことによって、実行時に想定しない型が用いられてしまう可能性を軽減できます。

なお注意として、例えばstringToNumber("a")とした場合は、"a"はstring型のため、コンパイルは問題なく成功します。しかし実行時、関数からは NaNが返ってきてしまいます。NaNはnumber型ではありますが(https://github.com/Microsoft/TypeScript/blob/7b9ceb85fa4e19ade740faa2af2e00e62e16f7c9/lib/lib.es5.d.ts#L25 )、関数の戻り値に対してNaNを期待しない場合にバグを生む可能性があります。このような例については(少なくとも上記のコードでは)TypeScriptの機能で静的にチェックすることができません。ですので、TypeScriptを使用したからといって100%実行時エラーがなくなるわけでは無い点については注意が必要かと思います。

メリット2: 型がドキュメントの補助になる

メリット1から派生する恩恵として、例えばJavaScriptにて、

function hogeToFoo(hoge) {
  
  // ...なにか煩雑な処理...

  return fuga;
}

という関数があった時、「この関数には何を引数として渡せば良いのだろう?」と疑問に思うこともあるかと思います。そんな時、JSDoc等を用いてコメントベースで引数の情報をコードに残しても良いですが、例えばTypeScriptなら、

https://www.typescriptlang.org/play/index.html#src=interface%20Hoge%20%7B%0D%0A%20%20%20%20a%3A%20number%3B%0D%0A%7D%0D%0A%0D%0Ainterface%20Fuga%20extends%20Hoge%20%7B%0D%0A%20%20%20%20b%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Afunction%20hogeToFoo(hoge%3A%20Hoge)%3A%20Fuga%20%7B%0D%0A%0D%0A%20%20%20%20%2F%2F%20...%E3%81%AA%E3%81%AB%E3%81%8B%E7%85%A9%E9%9B%91%E3%81%AA%E5%87%A6%E7%90%86...%0D%0A%20%20%20%20return%20%7B%20a%3A%20hoge.a%2C%20b%3A%20%22fuga%22%20%7D%3B%0D%0A%7D%0D%0A

interface Hoge {
    a: number;
}

interface Fuga extends Hoge {
    b: string;
}

function hogeToFoo(hoge: Hoge): Fuga {

    // ...なにか煩雑な処理...
    return { a: hoge.a, b: "fuga" };
}

として型情報を引数、戻り値に記述するだけで、この関数hogeToFooが、Hogeを引数に取りFugaを返す関数であることが一目瞭然となります。さらに、関数に適用する値についてはコンパイル時に静的チェックされるので、より実行時の安全が担保されます。

メリット3: 機能拡張・リファクタリングが容易

https://www.typescriptlang.org/ では、"JavaScript that scales."と標榜されています。この言葉が実感できる要素の一つとして、先述した静的型チェックの恩恵から生まれる、TypeScriptの機能拡張・リファクタリングの容易さがあるかと思います。例えば上記のコードにおいて、Hogeを拡張したいとします。すると、

interface Hoge {
    a: number;
    c: boolean; // プロパティを追加
}

interface Fuga extends Hoge {
    b: string;
}

function hogeToFoo(hoge: Hoge): Fuga {
    return { a: hoge.a, b: "fuga" };  // 型エラー!
}

Hogeに影響のある全ての部分において、このエラーがコンパイル時に発生します。つまり、Hogeの型付けを完璧に行っておけば、Hogeの拡張に対する影響範囲を漏れなく知ることができるということです。これで、開発時の心理的障壁を抑えることができます。

メリット4: ES2015以降のシンタックスが使える

TypeScriptにおいては、コンパイル時の設定でtarget: es5を指定することで、ES2015, ES2016, ... -> ES5の変換を行うことができます。例えば、オブジェクトのアサイン等を行うことができるスプレッドシンタックスを用いた場合は、

https://www.typescriptlang.org/play/index.html#src=const%20hoge%20%3D%20%7B%20a%3A%201%2C%20b%3A%202%20%7D%3B%0D%0Aconst%20fuga%20%3D%20%7B%0D%0A%20%20%20%20c%3A%203%2C%0D%0A%20%20%20%20...hoge%0D%0A%7D%3B%0D%0A%0D%0Aalert(fuga)%3B%0D%0A

const hoge = { a: 1, b: 2 };
const fuga = {
    c: 3,
    ...hoge
};

alert(fuga);

というコードが、

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var hoge = { a: 1, b: 2 };
var fuga = __assign({ c: 3 }, hoge);
alert(fuga);

と変換されます。Babelを用いれば実現できる機能ではありますが、TypeScriptにビルトインされているため手軽に使える点がメリットかと思います。

さいごに

以上、実際にプロダクション環境でTypeScriptを使用してメリットと感じた点をいくつか挙げてみました。 TypeScriptを使ってコードを書いてみたいという方は、ぜひお声掛けください。 次回はnrskさんの予定です。