TypeScript入門

TypeScript とは

まずは TypeScript の特徴を紹介いたします。

  • Microsoft が開発した OSS の言語
  • JavaScript を拡張した「型」がある言語
  • TypeScript と JavaScript は相互に変換( トランスパイル )可能
    • ただし型を厳密にすると JavaScript → TypeScript ができない
    • これを回避するために型をany 型 (何でも許容する型) がある
  • コンパイルする前に IDE が検知するため、結果的に実装は速くなる
  • オブジェクト指向言語

JavaScript 記述例

var x = 1;
  x = 'こんにちは';

TypeScript記述例

var x: number = 1;
  x = 'こんにちは';

変数宣言のあとに number という “型” があることに加えて、実行時には TypeScript の方はエラーがでます。 宣言と異なる型だからです。

では、 JavaScript から TypeScript にすると開発が楽になるのでしょうか。

  • 学習コストが高いので、急に開発効率が上がらない
  • ソースコードの保守性では確実にメリットがある
  • 大規模開発になれば生産性向上が見込める

“大規模” の定義が難しいところです。

TypesScript の型

TypeScript にはどんな型があるのでしょうか。

  • number: 数値型 (整数、少数の違いはない)
  • string: 文字列
  • boolean: 真偽値
  • null: null も型
  • any: すべての型を許容する
  • 配列

型の書き方

続いて、型の書き方について、サンプルコードをもとに見てみましょう。

・型の宣言

  • 変数名の横に書く
var num: number = 3;
var name: string = '田中';
var isLock: boolean = false;
var sample: any = 'テスト';

関数での書き方

// 戻り値がない実行型の場合
function sample(name: string): void {
}

// 戻り値がある場合
function sample(name: string): number {
    return 0;
}

・クラスでの書き方

  • constructor() は予約語
class Person {
  constructor(private name: string, age: number) {
  // 処理
  }
  purblic run(speed: number): void {
  // 処理
  }
}

型エイリアス

型の別名を定義する 型エイリアス も使えます。

// 型エイリアスは type を使って定義
type str = string;
var name: str = '田中';

単純に 1 つの型をエイリアスにするだけでなく、複数の値が入った連想配列も名前を付けられます。

type Person = { name: string, age: number };
var p: Person = {
  name: '田中',
  age: 25
}

こうすると、一時的なクラスのように扱えます。 C 言語の構造体のようですね。 ほかのクラスといっしょに小さいデータ構造を定義する場合や、クラスの中にプライベートクラスを定義するときなどに使えます。

TypeScript 入門演習

では、ここからは座学で学んだことをもとに演習しながら、確かめてみましょう。

インストール

まずはインストールからです。

$ npm install -g typescript

インストールできたか確かめてみます。

$ tsc --version
Version 4.6.2

トランスコンパイルしてみよう

TypeScript <–> JavaScript でトランスコンパイルしてみましょう。

class User {
    constructor(public userName: string, public userAddress: string) {
        console.log("constructor");
    }
    getInfo() : string {
        return "名前:" + this.userName + " 住所:" + this.userAddress;
    }
};
var user = new User('田中', '東京都');
var str = user.getInfo();
console.log(str);

コードの解説

  • 変数宣言や Setter/Getter 無しで construct するのが常道
    • 変数は public にする
このコードをトランスコンパイルします。
$ tsc Sample.ts

生成された JavaScript のソースコードを見てみます。

var User = /** @class */ (function () {
    function User(userName, userAddress) {
        this.userName = userName;
        this.userAddress = userAddress;
        console.log("constructor");
    }
    User.prototype.getInfo = function () {
        return "名前:" + this.userName
            + " 住所:" + this.userAddress;
    };
    return User;
}());
;
var user = new User('田中', '東京都');
var str = user.getInfo();
console.log(str);

prototype を使って継承しています。

これで node.js で実行できるようになったので、動かしてみましょう。

$ node Sample.js
constructor
名前:田中 住所:東京都

毎回トランスコンパイルするのは面倒なので、一発でコマンド実行できる ts-node をインストールします。

$ npm install -g ts-node

トランスコンパイルするための設定ファイルが必要なので、それを生成します。 package.json を作るのと同じです。

$ tsc --init

Created a new tsconfig.json with:

  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig.json

それでは、.ts ファイルを実行してみましょう。

$ ts-node Sample.ts 
constructor
名前:田中住所:東京都

これで完了です。

ジェネリクス

今度は型を試しながら、 TypeScript はもちろん Java や C# など静的型付け言語ではおなじみの ジェネリクス を使ってみます。

// JavaScript
function writeMessage(mes) {
    console.log(mes);
}
writeMessage('テスト');
writeMessage(2);

引数に型がいらない JavaScript では問題が起きないコードも TypeScript ではエラーになります。

// TypeScript
function writeMessage(mes: string) {
    console.log(mes);
}
writeMessage('テスト');
writeMessage(2); // エラーになる

そこで登場するのがジェネリクスです。

function writeGenerics<T>(mes: T): void {
    console.log(mes);
}
writeGenerics<string>('テスト'); // テスト
writeGenerics<number>(2); // 2
writeGenerics(3); // 3

コードの解説

  • 型が不明なときは T を使う
  • 利用するときに型を指定して引数に入れる
  • 型推論が効くので writeGenerics(3) はエラーにならない

実際のジェネリクスの使い方を見てみます。

class ItemAttr<T>{

  name: string;
  attr: T;
  
  constructor(name: string, attr: T){
      this.name = name;
      this.attr = attr;
  }

  getAttribute(): T {
      return this.attr;
  }
}
コードの解説
  • key を受け取って value に複数の型を取るクラス
  • getAttribute() の戻り値も複数の型

このクラスを使ってみましょう。

// 文字列を value に取るときは T を string に
var attr1 = new ItemAttr<string>("氏名", "田中");

// number を value に取るときは T を number に
var attr2 = new ItemAttr<number>("年齢", 25);

// 戻り値が string なら T を string にする
var namestr: string = attr1.getAttribute(); // 田中

// 戻り値が number なら T を number にする
var age: number = attr2.getAttribute(); // 25

「持ちたい値ごとに型を指定する」と補足しましたが、確かに変数に何が入るのか明示的になります。

オーバーロード

関数を複数定義して、コンテキストに合わせて使い分けたいときもあります。

function overloads(num: number): number {
    return 0;
}
function overloads(str: string): number {
    return 0;
}

当然ですが、これはエラーになります。 そこで登場するのが オーバーロード です。

1.インターフェイスを 2 つ書く

function overloads(num: number): number;
function overloads(str: string): number;

2.本体の処理は 1 つ

  ・引数は any にする

function overloads(value: any) {
    if (typeof value === 'number'){
        return 0;
    }
    if (typeof value === 'string'){
        return 1;
    }
    return -1;
}

実行してみましょう。

console.log("文字の場合:" + overloads("あああ")); // 文字列の場合:0

console.log("数値の場合:" + overloads(0)); // 数値の場合:1

とはいえ、ジェネリクスにせよ、オーバーロードにせよ、素の JavaScript で書くときより考えることが増えます。

TypeScript 実践演習 ~ フロントエンドで TypeScript を使ってみよう

TypeScript の特徴を学んだところで、実際にフロントエンドの開発で使ってみましょう。

環境構築

まずは環境構築からです。

・ビルドツールとして webpack を使う

  • 複数のファイルをまとめる
  • Web サーバにもなる

それでは、環境構築を進めます。

1.typescript と ts-loader をインストール

$ npm install typescript ts-loader

2.webpack / webpack-cli (webpack をコマンドラインで使用) / webpack-dev-server (開発用 Web サーバ) をインストール

$ npm install webpack webpack-cli webpack-dev-server

必要なものをインストールできたので、色々な設定ファイルを作成・編集します。

3.webpack でビルドして、サーバを動かせるよう package.json にコマンドを追加

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "buiild": "webpack --mode=development",
    "start": "webpack-dev-server --mode=development"
  },

npm run build でビルド「 npm start 」でサーバー起動

・build は予約語ではないのでコマンド時に run が必要になる

4.webpack のビルド手順をまとめた設定ファイル webpack.config.js を書く

  • ジェネレータが無く手動で書く
  • 今回は用意いただいたものを使用
const path = require('path');
module.exports = {
    // 指定できる値は、ファイル名の文字列や、それを並べた配列やオブジェクト
    // 下記はオブジェクトとして指定した例 
    entry: {
        bundle: './src/app.ts'
    },  
    output: {
        // "__dirname"はこのファイルが存在するディレクトリを表す node.js で定義済みの定数
        path: path.join(__dirname,'dist'),
        filename: '[name].js'  // [name]はentryで記述した名前(この例ではbundle)が入る
    },
    // 例えば「 import Foo from './foo' 」と記述すると "foo.ts" という名前のファイルをモジュールとして探す
    // デフォルトは['.js', '.json']
    resolve: {
        extensions:['.ts', '.js']
    },
    devServer: {
        static: path.join(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                // 拡張子が .ts で終わるファイルに対して TypeScript コンパイラを適用する
                test:/\.ts$/,loader: 'ts-loader'
            }
        ]
    }
}
  • entry でビルドを始めるファイルを指定
  • output はビルドしたときに生成されるファイルのファイル名と格納場所を指定
  • resolve でビルドに使うファイルの拡張子を指定
  • devServer に公開するディレクトリを指定
  • module でビルドするルールを記述。 今回は .ts ファイルを ts-loader でコンパイルする

5.ts ファイルを実行できる tsconfig.json をつくる

$ tsc --init

設定ファイルの準備が完了したので、ブラウザで表示するファイルを準備します。

6.HTML ファイルを格納する dist ディレクトリと、ソースコードを格納する src ディレクトリを作成

7.空でよいので src 配下に app.ts を作成

8.dist 配下に index.html を作成

・VS Code で html… と入力すれば自動補完で html テンプレートが入力される

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>Hello, TypeScript !!</h1>
</body>
</html>

それでは、実行してみます。

$ npm start

> type_01@1.0.0 start
> webpack-dev-server --mode=development

<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8080/
# 中略
webpack 5.70.0 compiled successfully in 3367 ms

localhost:8080 でも確かめてみましょう。

このように無事に動きました。

簡単な Web アプリケーションを開発

環境構築が完了したところで、簡単な Web アプリケーションを開発してみます。

今回のお題

  • 実装する Person クラス
    • write() メソッドを持つ
      • Person の情報を受け取って、それを HTML の要素にする

Person クラス (person.ts) を定義 → app.ts でインスタンス化 → index.html にデータ表示 とデータを受け渡すよう、作っていきます。

1.Person クラスを作り constructor() を書く

  • コンストラクタ引数は id / 名前 / 住所
class Person {
    constructor(
        private id: number,
        private personName: string,
        private personAddress: string
    ){}
}

2.write() メソッドを書く

export class Person {
    constructor(
        private id: number,
        private personName: string,
        private personAddress: string
    ){}
    
    // 引数に null が入る可能性があるので or null を入れる
    public write(elem : HTMLElement | null): void {
        // 要素が null なら何もしない
        if (elem) {
            // innerHTML で elem の中に要素を入れる
            elem.innerHTML += ''
            + 'ID:' + this.id 
            + ' 名前:' + this.personName 
            + ' 住所:' + this.personAddress
            + '';
        }
    }
}

3.環境構築で作った空の app.ts に Person クラスをインスタンス化して index.html に入れる処理を書く

import {Person} from './person';

// 画面から id="content" の要素を取得する。 なお getElementById() は空なら null を返す
var elem = document.getElementById('content');

// Person に情報を入れる
var person = new Person(1, '田中', '東京都');
var person = new Person(1, '鈴木', '千葉県');

// 情報が入った HTML 要素を作る
person.write(elem);

4.index.html に content エレメントを追加

<body>
  <div id="content"></div>
  <!-- app.ts がビルドされて bundle.js になる -->
  <script src="bundle.js"></script>
</body>

実行してみます。

ちなみに app.ts に新しいデータを追加して保存すると、リロードや再ビルドしなくても自動で更新されます。

React + TypeScript で開発してみよう

フロントエンド開発で主流となっている React と TypeScript を混ぜると、どうなるのでしょうか?

素の JavaScript との違いを体験してみましょう。

環境構築

TypeScript で React はトランスコンパイルできないので、これまでと環境構築が変わります。

1.create-react-app を使用する

  • TypeScript をテンプレートにしたプロジェクトも作成できる
  • ただし create-react-app はフロントエンドのみで開発される場合などで使用されることが多い
$ npx create-react-app type_02 --template typescript

2.type_02 というディレクトリが生成されるので移動して  npm start

ここでは、create-react-app を使いましたが、2 つのコマンドで完了しました!

React コンポーネントにおける TypeScript の書き方の違い

では、 React のコンポーネントを作って、 TypeScript と従来の Javascript との違いを体験しましょう。

  1. src 配下に components ディレクトリを作成
  2. コンポーネント Hello.tsx を作成
    • .tsx は JSX を含んだファイルの場合に使われる拡張子
    • コンポーネントは state や property を持つが、どれになるかは不明
    • ジェネリクスを使って解消する
    // 文字列型の name というプロパティを持つことを宣言 type Props = { name: string; } // プロパティがあるコンポーネントと認識させる export class Hello extends React.Component<Props> { public render() : JSX.Element { return <h1>こんにちは {this.props.name} さん!</h1> } };
  3. 一番最初にビルドする App.tsx を編集
    • Hello を import する
    • 表示する箇所に Hello を入れる
    import React from 'react'; import logo from './logo.svg'; import './App.css'; import { Hello } from './components/Hello' <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <Hello name='田中'></Hello> <p> Edit <code>src/App.tsx</code> and save to reload. </p>

では、実行してみましょう。

やりました!

ただ、素の JavaScript で React を書くときと比べると、コンポーネントの書き方一つとってもジェネリクスを使ったりと手間が増えます。 冒頭、冨原さんが “学習コストが高い” というのは慣れるのに時間がかかる、ということかも知れませんね。

create-react-app を使わない環境構築

先程は create-react-app を使って 2 コマンドで React 環境を構築しましたが、それではそれぞれのライブラリがどんな役割を果たしているのか、わかりません。

ここでは番外編として、 React 環境を一から構築してみましょう。

  1. npm init で package.json を作成
  2. webpack, webpack-cli, typescript, ts-loader をnpm install
  3. react, react-dom, @types/react, @types-react-dom をnpm install
    • React は TypeScript を使って開発されてない
    • そこで TypeScript の型定義を入れるライブラリ (@type~) を追加
  4. TypeScript のコンパイル設定をするため tsconfig.json を作成
    • カスタマイズが必要 (ES (ECMAScript: JavaSCript の規格) のバージョン指定など)
    "compilerOptions": { // 中略 "target": "ES5", "lib": ["ES2020", "DOM"], // 中略 "module": "ES2015", }
  5. ビルド手順をまとめた webpack.config.js を編集
    • .tsx のビルドルールやビルドで使用するファイルを指定
    module: { rules: [ { test: /\.tsx?$/, use: "ts-loader" } ] }, resolve: { extensions: [".ts", ".tsx", ".js", ".json"] },
  6. ビルドの起点となる /src/app.tsx を作成
  7. npm コマンドでビルドできるよう package.json を編集
    • scripts パラメータに build のコマンドを指定 (今回は build のみ使用)
    "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack", },

では、ビルドしてみましょう。

$ npm run build

> type_03@1.0.0 build
> webpack

asset main.js 1.21 KiB [emitted] (name: main)
./src/app.tsx 14 bytes [built] [code generated]
webpack 5.70.0 compiled successfully in 2518 ms

無事にビルドが通りました。

まとめ

  • TypeScript ならではの基本的な書き方、変数や戻り値の型、ジェネリクス、クラスやコンストラクタなどを覚えよう
    • ただしパターンは少ない
  • ライブラリには型定義 (.d.ts ファイル) がないものがあるので注意
    • 無い場合はライブラリのソースコードを読んで自分で型定義を書く必要がある

参考(日本語ドキュメント)

TypeScript入門 & 環境構築

なぜTypeScriptを使うのか?