10分でわかる「今更だけどTypeScriptってなに?」


2年ぶりくらいに業務で TypeScript をやることになったので、個人的なおさらい用です。

TypeScript とは

JavaScript は現在 Web アプリケーションの開発で最も使われているものの、JavaScript 独特の癖や、型の認識が緩いこと、ブラウザによって挙動に違いがあります。
それらを補うために altJS というアプローチがあります。JavaScript の機能を拡張し、よりメンテナンスしやすく、高機能にするものです。
それをトランスパイル(変換)することでブラウザによる挙動の違いを吸収したピュアな JavaScript が吐き出され、安全に JavaScript にはまだ無い機能を擬似的に使えるようにすることができます。

その中で現在デファクトスタンダードとなっているのが、TypeScriptです。

なぜ TypeScript が人気なのか

近年の Web 開発ではフロントエンドの領域が広がっており、ひとつのサービスを開発するのに比較的大規模な JavaScript の処理が求められます。
プログラミングは大規模になればなるほど管理することが難しくなってきます。
そのため、JavaScript に「型」とクラスベースのオブジェクト指向をもたらせてくれる TypeScript は近代の開発環境と非常に相性がいいのです。

人気の JavaScript フレームワークである Angular では標準採用しており、Vue や React といったライブラリも TypeScript をサポートしていますし、開発環境が非常に豊富であること(現在人気の IDE やテキストエディターでは基本的に対応している)、JavaScript の仕様である ECMAScript の機能がいち早く取り込まれ、一部の機能を実際に使うことができる点も多くのプロジェクトに採用されている理由のひとつです。

※ Angular の標準となっていたのに AngularJS と記載しておりました。申し訳ありません

TypeScript による機能拡張

静的型付けが可能に

TypeScript は JavaScript に型を付けることができます。
なぜ型があることが便利なのかというと、大規模な Web アプリケーションではデータが正しいかを保証できる仕組みが非常に役に立つためです。

データが正しいかどうか(不整合がないか)が検出できると、ちょっとしたミスによる不具合の発生が起きにくくなります。
コンパイル時にエラーで教えてくれるので保守性、堅牢性があがるということですね。

個人的に便利だな、と感じるのはコードをぱっと見たときにコメントがなくてもどういうデータを要求して、処理しようとしているのかがわかりやすいところでした。
これは複数人での開発や大規模な開発に役立ちそうです。

型についてよくわからない。。という方はこちらの記事が参考になります。
TypeScriptの型入門

型を他のライブラリにも適用するには

他の JavaScript ライブラリには型情報がないため、その橋渡し役として型定義ファイルを使います。
以前は tsd や Typings というツールを使って型定義ファイルを個別にインストールして管理していましたが、今は npm を利用して簡単にインストールし、npm のみで管理することができます。
インストールしたあとに特別な設定は必要ありません。

型定義ファイルをインストールするには以下のコマンドでインストールします。

npm install --save-dev @types/型定義ファイル名

例えば jQuery を使いたい場合は以下のコマンドでインストールすることができます。

npm install --save-dev @types/ jquery

関数

関数宣言の基本構文

function 命令による宣言

// 引数・戻り値に型を定義する(省略しても可)
function name(param: ptype, ...): rtype { ...statements... }

関数型による宣言

let name = function(param: ptype, ...): rtype { ...statements... };

アロー関数による宣言

ES2015でも採用された記法です。

// 単一行の場合
let name = (param: ptype, ...): rtype => ...statements...;

// 複数行の場合
let name = (param: ptype, ...): rtype => {
  ...statements...
}

関数型と似ていますが、this の扱いが違うため注意が必要です。
参考: https://qiita.com/mejileben/items/69e5facdb60781927929

デフォルト値

省略可能な引数は明示します。
省略方法は仮引数の名前の後ろに?を付与します。

function say(serif?: string = "Hi") {
  console.log(serif);
}
say();

引数にデフォルト値を設定する場合、それが省略可能な引数で、かつデフォルト値を持っているものは必ず必須の引数の後ろに記述しましょう。
デフォルト値には式も指定できます。自分より前に定義された引数も指定が可能です。
省略したときだけでなく、undefined (未定義の値)を明示的に指定した場合にも引数は省略したとみなされ、デフォルト値が適用されます。(nullは「データがない」ため適用されない)

可変長引数

仮引数の前に ...(ピリオドを3つ)を付与することで、可変長引数となります。
可変長引数は、渡された任意の数の引数を配列としてまとめて受け取ることができます。
型を定義する場合には number[] のように配列になるようにしましょう。

また、引数の数が未定でも受け取れるようになります。

関数のオーバーロード

C++ など、他の言語では一般的な機能です。
同じ関数名で、引数の型や数を柔軟に設定することができます。
引数の数、型をまとめたものをシグネチャといい、そのシグネチャを最初に複数定義してから、実装をひとつにまとめることができます。

// シグネチャを定義
function show(a: number): number;
function show(a: string, b: boolean): string;
// 定義したシグネチャの処理
function show(a: any, b: any): any {
  if (typeof a === "string") {
    return b ? "[true]" + a : "[false]" + a;
  }
  if(typeof a === "number"){
    return a;
  }
}

この機能によって、処理が同じ関数を引数の違いのためだけに別の名前で定義する必要がなくなります。
ただ、上の例を見てもわかるように定義したシグネチャの数だけ if 文での分岐が多くなるのでオーバーロードを使わずに実装したほうがいい場合も多くあります。

クラス

TypeScriptでは、クラスベースのオブジェクト指向プログラミングが可能になります。
基本構文はこのようになります。

class Person{
  name: string;
  age: number;
  constructor(name: string, age: number){
    this.name = name;
    this.age = age;
  }
  show() {
    console.log(`名前:${this.name} 年齢:${this.age}`);
  }
}

また、JavaScriptでは不要ですが、TypeScriptではプロパティの定義をクラス宣言の中で行う必要があります。定義していないプロパティアクセスはエラーになります。

継承/拡張(extends)

Extends を利用することで継承して拡張することも可能です。
先程定義した Person クラスを継承して Child クラスを作ってみます。

class Child extends Person{
  height: number;
  constructor(name: string, age: number, height: number){
    super.(name, age)
    this.height = height;
  }
  showHeight() {
    console.log(`身長:${this.height}`);
  }
}

アクセス修飾子

定義したクラスのプロパティやメソッドはどこからでもアクセスすることができます。
ただし、クラスの内部でのみ利用しているプロパティやメソッドに対してどこからでもアクセスできてしまうのは管理しづらくなり、思わぬ不具合を起こすこともあるため、アクセスを制限することができます。

TypeScript では、アクセス修飾子によって制限することができます。
アクセス修飾子は以下のようなものがあります。

  • Public: どこからでもアクセス可能。
  • Private: クラス内でのみアクセス可能。
  • Protected: クラス内、継承クラス内でのみアクセス可能。

アクセサ(getter/setter)

TypeScript には、「アクセサ」と呼ばれるものが用意されています。
これはプロパティの値を取得したり、設定したりする際に「どのようにアクセスされるか」を制御するための処理を用意するものです。

class Person {
  private _age: number;
    public get age(): number {
      return this._age;
    }
    public set age(src: number) {
      this._age = src;
    }
}

アクセサによって設定された値は private なプロパティに保管しておかなければなりませんが、自動的に保管されるわけではなく、上記のように別途 private なプロパティを作る必要があります。

インターフェイス

インターフェイスはすべてのメソッドが抽象メソッドである特別なクラスです。
また、一般的なクラスとは違い、複数のインターフェイスを同時に継承することができます。
class の代わりに interface というキーワードで宣言します。

interface name {...}

interface は全てのメソッドが抽象クラスなので、abstract は指定してはいけません。
すべてのメンバーは public であるのでアクセス修飾子も指定できないことに注意してください。

インターフェイスを実装するには extends ではなく implements キーワードを利用します。
ただし、インターフェイスを継承する際は extends を使用します。
さらに(TypeScript であれば)インターフェイスはクラスを継承することもできます。

また、インターフェイスは型注釈としても利用できます。

抽象クラス/メソッドに関してはこちらの記事がわかりやすいです。
https://qiita.com/hanako8/items/e8ddce85188970fd77da

まとめ

ここまでお読みいただきありがとうございます!
かなり「かっちりと」JavaScript が書ける印象ですよね。
小規模なものだと導入・運用コストが高くついてしまいますが、大きい規模のサービスでは絶大な効果をもたらしてくれそうです。

誤りや認識の違いなどがありましたらご指摘いただけると助かります!