TypeScriptを使いたいけど通常のJavaScriptとどんな違いがあるのかやJavaScriptとは全く異なる言語でまた一から勉強をしなければならないのかと疑問に思っている人もいるかと思います。本文書では公式のTypeScriptのドキュメントを参考し入門者を対象としてTypeScriptの基本について説明を行っています。

本文書を読み終えるとTypeScriptの型注釈(Type Annotation)とInterfaceの基礎を理解することができます。

TypeScriptとは

TypeScriptはJavaScriptに型の設定を行う機能が追加されたプログラミング言語です。JavaScriptとは異なる言語ではなく型の設定に関すること以外のコードについては通常のJavaScriptと同様の構文を使って記述することができます。

型がなぜそんなに大事かと疑問に思う人も多いかと思います。JavaScriptでは型を事前に宣言することがないので誤った型のデータを関数に渡している場合(文字列を渡さないければならないのに数値を渡す)実行中にエラーであることがわかります。しかし、TypeScriptは型を宣言するだけではなく事前にコンパイルを行う必要があるので、型に問題がある場合はコードを動作させる前に問題を把握することができます。またエディターによってはコードの記述中にエラーメッセージで型に問題があることを教えてくれるのでコード記述中に修正を行うことができます。また関数であれば引数、戻り値の型を設定することができるので他のプログラマーがコードを見てもどのような値を与えてどのような値が戻ってくるのかを理解することができます。

TypeScriptを利用する目的はエラーの少ない品質の高いコードを効率よく記述することにあります。JavaScriptを勉強を始めたばかりでまずはコードが動くことを目的にする場合はそれほど重要ではありませんし個人で開発するのであれば使わないという選択もありだと思います。しかし、対価をもらって行う仕事であれば納品するコードの品質が高ければ高いほどいいのは明白なのでTypeScriptを利用するということは選択肢に入ってきます。大規模なプロジェクトにプログラマーとして参加するためにはTypeScriptの理解は必須になります。

TypeScriptのインストール

nodeがインストールされている環境でnpmコマンドでグローバルにtypescriptのインストールを行います。


 $ npm install -g typescript

インストールが完了したらtsc -vを実行するとインストールしたTypeScriptのバージョンを確認することができます。


 % tsc -v
Version 4.4.2

最も簡単な例を使った動作確認

テストデータの作成

任意の名前のフォルダの作成してそのフォルダの下にgreeter.tsファイルを作成し下記のコードを記述します。TypeScriptのファイルの拡張子はtsです。


function greeter(person) {
  return "Hello, " + person;
}

let user = "Jane User";

document.body.textContent = greeter(user);

はじめてのコンパイル

greeter.tsファイルを作成後TypeScriptからJavaScriptファイルへの変換を行う必要があります。変換はtscコマンドを利用します。


 $ tsc greeter.ts 

実行が完了するとtscコマンドを実行したディレクトリ内にコンパイルが完了したgreeter.jsファイルが作成されます。letで定義したuserがvarに変更になっていることが確認できます。これはコンパイルによってES5に変換されるためです。ES6など他のバージョンに変換したい場合も可能で設定はTypeScriptの設定ファイルであるtsconfig.jsonファイルで行います。後ほどtsconfig.jsonファイルを作成して動作確認します。ES6からconst, letがサポートされます。


function greeter(person) {
    return "Hello, " + person;
}
var user = "Jane User";
document.body.textContent = greeter(user);
実際にgreet.jsファイルをindex.htmlファイルのscriptタグで読み込むと画面にJane Userが表示されます。変換前のgreeter.tsファイルをscriptタグで指定しても同じ結果になります。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./greeter.js"></script>
  </body>
</html>

watchオプション

コードを更新したらtscコマンドを再実行する必要がありますがwatchオプションを利用するとファイルの更新を監視してくれるのでコードを更新する度に手動でtscコマンドを実行する必要がなくなります。


 $ tsc -watch greeter.ts 
[13:56:02] Starting compilation in watch mode...

[13:56:03] Found 0 errors. Watching for file changes.

Type Annotation(型注釈)の追加

次にgreeter関数のpersonに型注釈を追加します。stringと追加したことでgreeterには文字列が入ることが宣言され、文字列を渡さない場合はエラーとなります。


function greeter(person: string) {
  return "Hello, " + person;
}

stringを追加してもこれまでのコードであればuserは文字列の”Jane User”なのでエラーが表示されることもなくtscコマンドは型注釈を追加する前と同様にコンパイルも完了します。

しかし、意図的にuserに配列を渡してtscコマンドでコンパイルを実行するとエラーが発生します。


let user = [0, 1, 2];

以下のように数値で構成された配列を入れることができないというエラーメッセージが表示されます。


 $ tsc greeter.ts 
greeter.ts:7:37 - error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

7 document.body.textContent = greeter(user);
                                      ~~~~
Found 1 error.

エラーが出力されてもJavaScriptへの変換は完了しgreeter.jsファイルは作成されます。

作成されるgreeter.jsファイルをindex.htmlファイルのscriptタグで読み込むと画面には、Hello, 0,1,2と表示され実行できないわけではありません。

型注釈を入れたgreeter.tsファイルをindex.htmlのscriptタグで読み込むとUncaught SyntaxError: Unexpected token ‘:’のエラーが発生します。

ここまでの動作で”TypeScriptはJavaScriptに変換しないと利用することができないこと“と“型注釈を設定することで誤った型のデータ挿入を未然に防ぐことができること”が確認できました。

TypeScriptの設定ファイルtsconfig.jsonファイルの作成

TypeScriptの設定ファイルはtsc –initコマンドで作成することができます。


 % tsc --init
message TS6071: Successfully created a tsconfig.json file.

実行後に実行したフォルダ内にtsconfig.jsonファイルが作成されます。ファイルを開くと各種設定が記述されています。tsconfig.jsonファイルを作成後はファイル名を指定しなくてもtscコマンドを実行するたけでコンパイルが行われます。


 % tsc 

最初にコンパイルをした時にletで定義した変数がコンパイル後はvarになっていました。tsconfig.jsonファイルの先頭にあるtargetをes5からes6に変更するとコンパイル後もletになります。


{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Enable incremental compilation */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./",                          /* Specify the folder for .tsbuildinfo incremental compilation files. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es6", 
//略

es5からes6に変更してtscコマンドを実行して作成されるgreeter.jsファイルを確認すると先頭に”use strict”とletが使われていることが確認できます。


"use strict";
function greeter(person) {
    return 'Hello, ' + person;
}
let user = [0, 1, 2];
document.body.textContent = greeter(user);

Interface(インターフェイス)とは

TypeScriptのInterfaceはオブジェクトの形を決めるルールのようなものでオブジェクトのプロパティの名前と型を定義することができます。

Interfaceの最もシンプルな使い方

例えばPersonというInterfaceを作成します。


interface Person {
    firstName: string;
    lastName: string;
}

PersonはfirstNameとlastNameの2つのプロパティを持ち、どちらの型もstringを持ちます。

先程のgreeter.tsファイルのgreeter関数の引数をオブジェクトに変更し、そのオブジェクトの型注釈に作成したインターフェイスを利用することができます。


function greeter(person: Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}

Interfaceを型注釈で指定するとgreeter関数を実行する際に挿入するオブジェクトはfirstNameとlastNameを持ちその型はstringである必要があります。


let user = { firstName: "Jane", lastName: "User" };

document.body.textContent = greeter(user);

ここまでのコードをまとめてコンパイルを実行するとこれまでと同様にindex.htmlでgreeter.jsファイルを読み込むと画面上にはHello Jane Userが表示されます。


interface Person {
  firstName: string;
  lastName: string;
}

function greeter(person: Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: "Jane", lastName: "User" };

document.body.textContent = greeter(user);

プロパティ名や値を変更してエラーを確認

もしfirstNameではなくFullNameにした場合にどのようなエラーになるか確認しておきましょう。


let user = { fullName: "Jane", lastName: "User" };

コンパイルを実行するとプロパティのfirstNameがないとエラーが表示されます。


$ tsc greeter.ts 
greeter.ts:12:37 - error TS2345: Argument of type '{ fullName: string; lastName: string; }' is not assignable to parameter of type 'Person'.
  Property 'firstName' is missing in type '{ fullName: string; lastName: string; }' but required in type 'Person'.

次にlastNameの値を数字にしてみましょう。


let user = { firstName: "Jane", lastName: 20 };

コンパイルを実行すると先程のプロパティ名を変更した時と異なるエラーが表示されます。値がnumberなのでstringではないというエラーになっています。


 $ tsc greeter.ts 
greeter.ts:13:37 - error TS2345: Argument of type '{ firstName: string; lastName: number; }' is not assignable to parameter of type 'Person'.
  Types of property 'lastName' are incompatible.
    Type 'number' is not assignable to type 'string'.

このようにInterfaceは設計図のような役割を持ちその設計図通りになっていない場合にはエラーが表示されます。

変数userを設定する場合にもInterfaceを利用することができます。


let user: Person = {
    firstName: "John",
    lastName: "Smith"
}

ClassにInterfaceを利用する

Classを作成する際にInterfaceを利用することができます。

作成したInterfaceを利用してClassを作成したい場合はimplementsを使って記述することができます。

Interfaceは先程設計図のような役割といったとおりInterfaceをimplementsしたクラスStudentはfirstNameとlastNameを持つ必要があります。


interface Person {
    firstName: string;
    lastName: string;
}

class Student implements Person {
  public firstName: string;
  public lastName: string;
}

// publicがなくてもOK
class Student implements Person {
  firstName: string;
  lastName: string;
}

プロパティがない場合

もしlastNameがない場合はコンパイル時にエラーが発生します。


class Student implements Person {
  public firstName: string;
}

 $ tsc greeter.ts 
greeter.ts:20:7 - error TS2420: Class 'Student' incorrectly implements interface 'Person'.
  Property 'lastName' is missing in type 'Student' but required in type 'Person

publicではなくprivateを使用した場合

publicではなくprivateを指定した場合も確認しておきましょう。


class Student implements Person {
  private firstName: string;
  public lastName: string;
}

以下のようにprivateに設定していることでエラーが発生しています。


 $ tsc greeter.ts 
greeter.ts:25:7 - error TS2420: Class 'Student' incorrectly implements interface 'Person'.
  Property 'firstName' is private in type 'Student' but not in type 'Person'.

プロパティに”?”がついた場合(optional properties)

地域によっては名前にmiddleNameを使う場合もあります。その場合に備えmiddleNameを追加したInterfaceを作成します。追加した場合はInterface Personをimplementsする際にはプロパティにmiddleNameが必要となります。


interface Person {
  firstName: string;
  middleName: string;
  lastName: string;
}

class Student implements Person {
  firstName: string;
  middleName: string;
  lastName: string;
}
もしStudentクラスにmiddleNameがなかった場合は、コンパイル時にerror TS2420: Class ‘Student’ incorrectly implements interface ‘Person’.
Property ‘middleName’ is missing in type ‘Student’ but required in type ‘Person’.のエラーが表示されます。

しかしmiddleNameを使わない地域によってはmiddleNameはオプションなので必要ではありません。必須ではない(オプション)プロパティに”?”をつけるとStudentクラスでmiddleNameがなくてもエラーになりません。


interface Person {
  firstName: string;
  middleName?: string;
  lastName: string;
}

class Student implements Person {
  firstName: string;
  lastName: string;
}

Classを使ってgreeter.tsを作成する

最初は少し混乱してしまうかもしれませんがClassとInterfaceを利用してgreeter.tsを作成します。

class Studentを作成していますが、constructorでfirstNameとlastNameでpublicを設定しています。

firstNameとlastNameにpublicをつけない場合はエラーになります。

interface Person {
  firstName: string;
  lastName: string;
}

class Student {
  fullName: string;
  constructor(
    public firstName: string,
    public middleInitial: string,
    public lastName: string
  ) {
    this.fullName = firstName + " " + middleInitial + " " + lastName;
  }
}

function greeter(person: Person) {
  return "Hello, " + person.firstName + " " + person.lastName;
}

let user = new Student("Jane", "M.", "User");

document.body.textContent = greeter(user);

index.htmlファイルでgreeter.jsを読み込むと画面にはHello, Jane Userが表示されます。

console.logを利用してStudentクラスから作成したuserオブジェクトの中身を見るとfistNameとlastNameなどを確認することができます。

userオブジェクトの中身
userオブジェクトの中身

シンプルなコードを利用してTypeScriptの型注釈とInterfaceとClassについての基本を確認することができました。