TypeScript 入門勉強会

この勉強会の特徴

こんにちは。フリーランスエンジニアのはるやです!

私は主にVue.js / TypeScriptを使ったフロントエンドの開発をしています!

まず、当勉強会の概要についてご説明します。

この勉強会では、TypeScript という JavaScript の進化版のような言語について学習していきたいと思います!

この勉強会はこんな方におすすめです!

  • JavaScript の学習をある程度終わった方で、さらにモダンな技術を習得したい方
  • 静的型付け言語を使って安全に開発をしたい方
  • 近年 JavaScript を卒業して TypeScript で開発をしたいと言う Web 開発現場の需要に答えたい方
  • フリーランスエンジニアになって高単価の案件を獲得したい方

TypeScript の特徴

冒頭でも説明しましたが TypeScript(以下TS) は JavaScript(以下JS) の進化版のような物です!(モンハンで言うとリオレウス亜種みたいな感じです)

特徴としては

  • JS との構文で型定義ができる
  • 出来ることもほぼ同じ
  • 既に JS を勉強済みの方にとっては学習コストがかなり低い

のが特徴です!

(簡単に言うと JS の型が使える版です。静的型付け言語と言います)

また、先ほどもお伝えしましたが最近ではTSの需要も高まって来ているのもあり

フリーランス案件の単価が比較的高い傾向にあるので単価をUPしたい方にもおすすめです!

静的型付け言語とは?

静的型付け言語とは変数に型をつける事で、プログラムの実行前の段階で予期せぬエラーを未然に防ぐことが出来るのが特徴です!

詳しくはこの後実際に使ってみながら理解していくので

なんとなく安全に開発が出来るんだなーぐらいに思ってください!

今回作るもの

まずは TS の基礎的な文法を学習しながら

TODO アプリのような物を作っていきたいと思います!

代替テキスト

事前準備(MacOS の方は除く)

MacOS の方以外は、下記の事前準備を行いましょう。

Windows の方

Windows ユーザの方は、下記のページを参考に、Git Bash をインストールしましょう。

Windows でも、後ほど出てくる Linux コマンドの一部を打てるようにするためです。

Windows に Git Bash をインストールする

参考記事

AWS Cloud9 の方

万一、Windows, Mac でも上手く動作しないという場合の対応として、AWS Cloud9 を利用するという方法もあります。

下記の操作方法を見ながら、Cloud9 上で操作してみて下さい。

AWS アカウントを作成しよう

Cloud9 の利用には、まず AWS アカウントが必要です。
下記のページにアクセスして、下記登録手順を参考に、自分のアカウントを作りましょう。

※作成にはクレジットカードが必要ですが、講義で使う Cloud9 ワークスペース1つなら
 仮に「1 年間」毎日フルで使い続けても無料です。

AWS 公式ページ

アカウント登録
参考記事

※参考

AWS 公式ページ アカウント登録手順

参考記事

Cloud9 ワークスペースを作成しよう

AWS アカウントが作成できたら、下記サイトを参考に、講義用の「ワークスペース(開発スペース)」を作ってみましょう!

【AWS Cloud9 の使い方】最初に覚えておくべき機能まとめ

参考記事

プラットフォームのタイプ(OS)を選択する際は、下記の画像の通り、「 Amazon Linux 2 」ではなく、「Amazon Linux」を選択しましょう。

cloud9メニュー

Cloud9 の Node.js のバージョンを上げる

ワークスペースの作成が完了しましたら、次に、下記の記事を参考に Node.js のバージョンアップを行ってください。

参考記事

VScodeの準備

MacとWindows環境で開発をされる方はVScodeで開発をしていきたいと思います。

まだVSCODEが入っていない方は公式ページからインストールをよろしくお願いします。

参考記事

TypeScript の環境構築をしよう!

Node.js の確認

まず、ご自分の PC に Node.js がインストールされているかを確認して下さい。

下記コマンドで確認いただけます。

Node.js のインストールの確認

 node -v
 npm -v

上記のコマンドで、それぞれのバージョンが表示されれば OK です。

基本的に、Node.js がインストールされていれば、NPM もインストールされているはずです。

まだインストールされていない方は、下記の手順を参考にインストールしましょう。

nodebrew をインストール(Mac の方)

nodebrew とは?

  • Node.js のバージョン管理ツール
  • 複数のバージョンを使い分けるために必要

参考記事

前提 homebrew が入っている

homebrew -vで homebrew が入っているか確認し
なければ下記記事を参考にインストールをお願いします。

参考記事

nodebrew をインストール

brew install nodebrew

インストールの確認

nodebrew -v

環境変数を追加

vi ~/.bash_profile

vi エディタが開くのでiを押してインサート(入力)モードに変更

下記をコピペ

export PATH=$HOME/.nodebrew/current/bin:$PATH

esc キーを押した後に:wqを入力

bash_profile を更新して設定を反映させる。

source ~/.bash_profile

セットアップ

nodebrew setup

インストール可能なバージョンを確認

nodebrew ls-remote

node のインストール

nodebrew install-binary <version>

nodebrew install-binary v14.15.5 // 今回はv14.15.5をインストール

インストールしたバージョンを確認

nodebrew ls

使いたいバージョンを指定

nodebrew use v14.15.5

node -v

Node.js のインストール(Windows の方)

参考記事

Node.js 公式(※こちらから推奨版をインストールしましょう)

Node.js公式サイト

Node.js のインストール(Cloud9 の場合)

参考記事

TypeScript をインストール

下記コマンドを実行して TS をインストール

npm install -g typescript

パーミッションエラーが出る場合

パーミッションエラーが発生したら、下記のコマンドで再実行してください。

sudo npm install -g typescript

インストールが完了したら、下記できちんとインストールできているか、バージョン確認を行いましょう。

tsc -v

バージョンが表示されたらインストールが完了です。

TypeScript 基礎

TS ファイルの作成

それではいよいよ TS での開発をしていきます。

TS では.tsと言う拡張子を付けることで TS での開発をすることが出来ます。

まずは今回の基礎学習用のフォルダとファイルを作成します。

下記コマンドを実行してください。(エディターのボタン操作で作ってもらっても大丈夫です。)

mkdir basic

cd basic

touch 01-primitive-types.ts

TS の設定ファイルの作成

TS で開発をするためにはtsconfig.jsonと言う

TS の設定を記述するファイルが必要です。

自力で作成することも出来ますが、自動で設定の中身まで作ってくれる

便利なコマンドがあるので今回はそちらを使っていきます。

basicディレクトリにいることを確認して、下記コマンドを実行してください。

tsc --init

コマンド実行後tsconfig.jsonと言う名前のファイルが作成されていれば成功です。

基本的にデフォルトの設定のままで大丈夫なので、中身の説明は割愛します。

もし気になる方は下記の記事をご覧ください!

参考記事

TypeScript の開発のメリット

  • 入力補完が使える
  • どんなデータを渡せばよいか一目でわかる
  • エラーチェックができる

TS では JS でもよく使う変数を宣言する際に

string 型や number 型などの型を定義することが出来ます。

静的型付け言語の経験がない方はイメージしにくいと思うので

早速、使ってみましょう

基本的な型宣言

まず先ほど作成した01-primitive-types.tsに下記のコードを貼り付けてください。

// 変数の宣言にはletが必要
// let (変数名): (型名) = 値
let userName: string = "山田太郎" //(文字列)
let age: number = 20 //(数値)
let isAdult: boolean = true // (真偽値)
let test: any = "AAA" // なんでもOK(JSと同じ)

console.log(userName)
console.log(age)
console.log(isAdult)
console.log(test)

userName = "田中花子"
// userName = 1 // userNameがstring型なのでエラーになる
age = 30
// age = "30" // エラーになる

// anyは何でも代入することが出来てしまう
test = "テスト"
test = 0
test = false

// 型宣言は省略も可能
let testName = "テストネーム"
// testName = 0 // エラーになる

貼り付けてみたら、コメントにしている箇所を外してみて実際にどのように動くか試してみてください!

例えばstring型に宣言したuserNameに 1 などの数字を入れようとするとエラーになると思います。

今はまだファイルの量が少ないので恩恵を感じ辛いですが、これが実務レベルになると

ファイルやソースコードも膨大になっていきます。

ソースコードが膨大になると、JS などの動的型付け言語を使っている場合、変数が全てany型となってしまういます。

例えばうっかりnumber型(数字を扱う)変数にboolean型(true false)を入れてしまうなどのヒューマンエラーが起きやすくなってしまいます。

TS などの静的型付け言語ではこういったヒューマンエラーをプログラムの力で少なくすることが出来るのが最大のメリットです。

記述量は JS に比べると少し増えてしまいますが、ヒューマンエラーによるバグを事前に防ぐことで

バグ解決のための使われていた工数を大幅に削減できるので、プロジェクトの規模が大きいほど恩恵を受けることが出来ます。

TS を JS にコンパイルして動作確認

冒頭で TS は JS の進化版のような言語と説明しましたが

TS が具体的にどのような仕組みで動作しているのかを説明していきたいと思います!

TS のソースコード(.ts)は、実はそのままでは実行できず、一度JSにコンパイル(変換)する必要があります。

以下の様に tscコマンド を使用する事でコンパイルすることが出来ます!

tsc 01-primitive-types.ts

コマンドを実行したら01-primitive-types.jsと言う名前のファイルが作成されたと思います。

中身を確認すると先ほど TS で書いたコードが JS に変換されている事が分かるかと思います。

この JS ファイルを Node.js を使って実行していきたいと思います。

Node.js ではnode (ファイル名.js)で js ファイルを実行する事ができます。

下記コマンドを実行してください。

node 01-primitive-types.js

コマンドを実行すると下記のようにconsole.log()の中身がターミナルに表示されたかと思います。

山田太郎
20
true
AAA

今度はコメント化している// userName = 1のコメントを解除してわざとエラーが出る状態にします。

この状態でもう一度下記のコンパイルコマンドを実行してください。

tsc 01-primitive-types.ts

下記のようにエラーになったかと思います。

01-primitive-types.ts:15:1 - error TS2322: Type 'number' is not assignable to type 'string'.

15 userName = 1 // エラーになる
   ~~~~~~~~


Found 1 error.

このように TS ではコンパイルの際に構文が正しいか自動でチェックする事が出来るので

予期せぬエラーを未然に防ぐ事ができます。

オブジェクトの型宣言

オブジェクトの型宣言の方法を解説していきたいと思います。

下記のコマンドで02-object-array.tsを作成してください。

touch 02-object-array.ts

作成した02-object-array.tsに下記ソースコードをコピペしてください。

// オブジェクトの型定義をするには`interface`を使います。
// 構文は下記の通りです。

// interface 名前(先頭大文字) {
//  key名: 型名
//}

// 例
interface User {
  firstName: string
  lastName: string
  age: number
  isAdult: boolean
  remarks?: string // 必須じゃない場合は?を付ける
}

// オブジェクトの型定義をする時の構文は下記の通りです。
// const 変数名: interface名 = {
//   key名: value
// }

// 例
const user: User = {
  firstName: "山田",
  lastName: "太郎",
  age: 22,
  isAdult: true,
  //   remarks: "備考", // 有っても無くもOK
  //   test: "テスト", // Userインターフェイスに無いkeyはエラーになる
}
console.log(user)

オブジェクトの型宣言の方法を解説していきたいと思います。

下記のコマンドで02-object-array.tsを作成してください。

tscコマンドでjsファイルに変換して実行してみましょう!

# -wオプションを使えば毎回tscコマンドを使わなくても
# tsファイルの変更を検知したら自動でコンパイルしてくれます!
tsc 02-object-array.ts -w

node 02-object-array.js

下記のように出力されたら成功です!

{ firstName: '山田', lastName: '太郎', age: 22, isAdult: true }

配列の型宣言

次に配列の型宣言の方法を解説していきたいと思います。

下記のコードを02-object-array.tsに追加してください。

// 配列の型定義をする場合は下記の通りです。
// let 変数名: 型名[] = 配列

// 例

// 数字の配列の場合
let numbers: number[] = [0, 1, 2, 3]

// 配列に値を追加するpushを使う場合
// numbers.push("AAA") // 型が違うのでエラーになる
numbers.push(4)
console.log(numbers)

// 文字列の配列
let strings: string[] = ["A", "B", "C", "D"] //数字の配列
// strings.push(4) // 型が違うのでエラーになる
strings.push("E")
console.log(strings)

// 配列とオブジェクトを組み合わせる場合は
// interface名[]で定義できるは

// 例 一覧データの表示などよく使います。
const users: User[] = [
  {
    firstName: "山田",
    lastName: "太郎",
    age: 20,
    isAdult: true,
    //   test: "テスト", // Userインターフェイスに無いkeyはエラーになる
  },
  {
    firstName: "田中",
    lastName: "花子",
    age: 19,
    isAdult: false,
  },
]
console.log(users)

下記コマンドを実行して出力を見てみましょう。

node 02-object-array.js

関数 の型定義

次に関数の型宣言の方法を解説していきたいと思います。

下記のコマンドで03-function.tsを作成してください。

touch 03-function.ts

下記のコードを03-function.tsに追加してください。

// <関数の型宣言>

// TSの関数は引数と返り値(戻り値)に対して型を定義する事ができます。

// 構文は
// function 関数名(引数名: 型名): 戻り値の型名 

// 例
function plus(a: number, b: number): number {
  return a + b
  //   return "a + b" //戻り値の型が違うのでエラー
  // 戻り値の型を指定している場合、戻り値がなくてもエラーに
}

let total = plus(1, 2)
// let total2 = plus() //引数が足りないのでエラーになる
// let total3 = plus("1", "2") //引数の型が違うのでエラーになる
console.log(total)

// interfaceを使うとオブジェクトの引数や戻り値を定義することもできます。

// 例
interface User {
  firstName: string
  lastName: string
  age: number
  isAdult: boolean
  remarks?: string // 必須じゃない場合は?を付ける
}

function greet(user: User): string {
  return "こんにちは" + user.firstName + user.lastName + "さん"
}

const greetUser: User = {
  firstName: "山田",
  lastName: "太郎",
  age: 22,
  isAdult: true,
}

const hello = greet(greetUser)
// let hello2 = greet() //引数が足りないのでエラーになる
// let hello3 = greet("1") //引数の型が違うのでエラーになる
console.log(hello)

下記のコマンドでコンパイルしてNodeを実行してみましょう!

# ファイル名を指定しなければカレントディレクトリ直下の
# 全てのtsファイルがコンパイルされる
tsc -w

node 03-function.js

# 下記のように表示されればOKです。
3
こんにちは山田太郎さん

class の型定義

次にclassの型宣言の方法を解説していきたいと思います。

下記のコマンドで04-class.tsを作成してください。

touch 04-class.ts

次にname,ageを渡したら成人かどうかを判定するclassを作っていきたいと思います。

下記のコードを04-class.tsに追加してください。

// <classの型宣言>

// classの型定義にはオブジェクトの型定義にも使用したinterfaceを使用します。

// またinterfaceには関数を定義することも出来ます。

// 例
interface PersonClass {
  name: string
  age: number
  isAdultCheck(): string // 関数も型
}

// classに型を定義する時の構文は下記のようになります。

// class クラス名 implements interface定義 {}

// 例
//implementsでclassの型を定義
class Person implements PersonClass {
  // クラスの型宣言
  public name: string
  //   public name: number //型が違うのでエラーになる
  public age: number

  // constructorの引数に型宣言
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  // クラスのメソッド(関数)に型宣言
  public isAdultCheck(): string {
    //   public isAdultCheck(): number {
    if (this.age >= 20) {
      return `${this.name}は成人です。`
    } else {
      return `${this.name}は未成年です。`
    }

    // elseは出来るだけ使わないように
    // if (this.age >= 20) {
    //   return `${this.name}は成人です。`
    // }
    // return `${this.name}は未成年です。`
  }
}

const taro = new Person("太郎", 30)
console.log(taro.isAdultCheck())

const hanako = new Person("花子", 15)
console.log(hanako.isAdultCheck())

下記のコマンドでコンパイルしてNodeを実行してみましょう!

node 04-class.js

# 下記のように表示されればOKです。
太郎は成人です。
花子は未成年です。

このようにTSを使ったclassではそのclassに実装が必要な変数や関数を定義する事で漏れなどを防ぎます。

まとめ

TSの基礎は以上になります!

ここまでで学習した内容を簡単におさらいしましょう。

・TSはJSの記述で型が使える言語

・型を使うことでヒューマンエラーを未然に防ぐ事ができる

・TSはtscコマンドを使ってJSに変換してから実行

// 変数の宣言
let 変数名 :型名 = 値

// interfaceの宣言
interface 名前(先頭大文字) {
 key名: 型名
}

// オブジェクトの宣言
const 変数名: interface名 = {
  key名: value
}

// 配列の宣言
let 変数名 :型名[] = 配列の値

// 関数名の宣言
function 関数名(引数名: 型名): 戻り値の型名 {}

// クラスの宣言
class クラス名 implements interface定義 {}

次の章ではここまで学習した内容を使って簡単なTODOアプリを作っていきます!

TypeScript と HTML を使ったフロント開発

先ほどまではバックエンドで動かしていましたが

今度はTSとHTMLを使ってフロントエンドの開発をしていきたいと思います。

※ 今回はTSの使い方をメインに解説するので

HTMLやCSSなどの解説については割愛させて頂きます🙇‍♂️

Webpack を使って環境構築

Webpackとは?

今回、フロントの環境構築にWebpackと言うライブラリーを使っていきたいと思います。

Webpackについては下記の記事で解説されています!

参考記事

また、ts-loaderと言うライブラリーと組み合わせて使うと

先ほどの基礎編の時のように、いちいちJSファイルに変換するコマンドを実行しなくてもWebpackが自動で一つのJSファイルにバンドルしてくれます。

(バンドルとはまとめると言う意味です。)

環境構築

現在basicフォルダにいると思うので下記コマンドで01-ts-basic直下にtodo-appと言う名前のフォルダを作成してください。

cd ..

mkdir todo-app && cd todo-app

srcフォルダとindex.tsを作成

mkdir src && touch src/index.ts

tsconfig.tsとpackage.jsonを作成

tsc --init
npm init

Webpack開発に必要なライブラリのインストール

npm install --save-dev typescript ts-loader webpack webpack-cli webpack-dev-server

次にwebpackの設定ファイルを作成

touch webpack.config.js

作成したwebpack.config.jsに下記のコードを貼り付けてください。

const path = require('path');
module.exports = {
    // モジュールバンドルを行う起点となるファイルの指定
    // 指定できる値としては、ファイル名の文字列や、それを並べた配列やオブジェクト
    // 下記はオブジェクトとして指定した例
    entry: {
        bundle: './src/index.ts'
    },
    // モジュールバンドルを行った結果を出力する場所やファイル名の指定
    output: {
        path: path.join(__dirname, 'dist'), // "__dirname"はファイルが存在するディレクトリ
        filename: '[name].js'  // [name]はentryで記述した名前(この設定ならbundle)
    },
    // import文でファイル拡張子を書かずに名前解決するための設定
    resolve: {
        extensions: ['.ts', '.js'] // Reactの.tsxや.jsxの拡張子も扱いたい場合は配列内に追加する
    },
    devServer: {
        contentBase: path.join(__dirname, 'dist'), // webpack-dev-serverの公開フォルダ
        open: true // サーバー起動時にブラウザを開く
    },
    // モジュールに適用するルールの設定(ローダーの設定を行う事が多い)
    module: {
        rules: [
            {
                // 拡張子が.tsのファイルに対してTypeScriptコンパイラを適用する
                // Reactで用いる.tsxの拡張子にも適用する場合は test:/\.(ts|tsx)$/,
                test: /\.ts$/,
                loader: 'ts-loader'
            }
        ]
    }
}

こちらはWebpackを使用する上での設定を記述しています。

今回は詳しい解説は割愛します、詳細が気になる方はコメントで解説しているので見てみてください。

HTMLファイル作成

HTMLファイルとdistフォルダ作成

mkdir dist && touch dist/index.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>TODOリスト</title>
</head>

<body>
    <div>
        <h1>TODOリスト</h1>
        <div>
            <label for="task-name">タスク名</label>
            <input type="text" id="task-name">

            <label for="task-name">タスク詳細</label>
            <input type="text" id="task-detail">

            <button id="create-task-btn">登録</button>
        </div>
    </div>
    <br>
    <!-- <table> -->
    <table id="table">
        <thead>
            <tr>
                <th class="min-area">No</th>
                <th>タスク名</th>
                <th>タスク詳細</th>
                <th class="min-area"></th>
            </tr>
        </thead>
        <tbody id="task-list">
        </tbody>
    </table>
    <!-- XSSサンプル -->
    <!-- <div>1億円プレゼント!応募する?</div><a href="https://www.google.com/" style="color: #fff;background: #5876a3;padding: 10px 30px;display: inline-block;border-radius: 5px;text-decoration: none;">はい</a><a href="https://www.google.com/" style="color: #fff;background: #767e8b;padding: 10px 30px;display: inline-block;border-radius: 5px;text-decoration: none;">いいえ</a> -->
    <!-- <script type="text/javascript">document.getElementById("table").onclick = function () { alert("XSS") }</script> -->

    <!-- webpackでバンドルされたファイルを読み込み -->
    <script src="./bundle.js"></script>
</body>

</html>

<style>
    table,
    td,
    th {
        border: 1px solid #333;
    }

    td {
        background-color: #ddd;
    }

    th {
        width: 200px;
    }

    .min-area {
        width: 45px;
    }

    thead {
        background-color: #333;
        color: #fff;
    }
</style>

package.jsonのscript書き換え

  "scripts": {
    "build": "webpack --mode=production",
    "start": "webpack-cli serve --mode development"
  },

サーバー起動

npm run start

下記のような画像が表示されたら環境構築は完了です!

代替テキスト

このままではボタンを押しても何も起きないので

ボタンをクリックした時にタスクが登録されるアプリを作っていきたいと思います。

(バックエンドやローカルストレージの実装はしないので画面を更新するとリセットされます。)

TODOアプリを作っていこう!

ボタンクリック時のイベントを追加

まずはボタンがクリックされた時のイベントを作っていこうと思います!

index.tsに下記コードを追加してください。

// idがcreate-task-btnの要素を取得してcreateTaskBtnに代入
const createTaskBtn = document.getElementById("create-task-btn")

// TSではnullやundefinedの可能性がある物を参照する場合
// エラーで弾かれる
// createTaskBtn.onclick = function () {
//   tasks.createTask()
// }

// nullやundefinedの可能性がある物を参照するには
// if文などで存在することをチェックしないと使えない。
if (createTaskBtn) {
  // 取得した要素をクリックした時の処理を定義
  createTaskBtn.onclick = function () {
    alert("ボタンをクリック")
  }
}

task class作成

既に実務経験のある方なら分かる方も多いと思いますが

基本的に一つのファイルが肥大化しないように機能ごとにファイルを分割します。

まずは下記コマンドでtasks.tsと言う名前のファイルを作成してください。

touch src/tasks.ts

次に作成したtasks.tsTasksと言う名前のclassを作成して、その中にcreateTaskと言う名前の関数を作成します。

tasks.ts

export class Tasks {
  public createTask() {
    // 動作確認のためalert()を追加
    alert("createTask")
  }
}

JSやTSではexportを付けることで、別ファイルで使用できるようになります。

index.tsインポートして、登録ボタンが押された時にcreateTaskが呼ばれるようにしましょう!

index.ts

// tasks.tsからTasksをインポート
import { Tasks } from "./tasks"
const tasks = new Tasks()

// idがcreate-task-btnの要素を取得してcreateTaskBtnに代入
const createTaskBtn = document.getElementById("create-task-btn")

// TSではnullやundefinedの可能性がある物を参照する場合
// エラーで弾かれる
// createTaskBtn.onclick = function () {
//   tasks.createTask()
// }

// nullやundefinedの可能性がある物を参照するには
// if文などで存在することをチェックしないと使えない。
if (createTaskBtn) {
  // 取得した要素をクリックした時の処理を定義
  createTaskBtn.onclick = function () {
    tasks.createTask()
  }
}

これで登録ボタンを押した時にcreateTaskが呼ばれるようになりました。

ここからは登録ボタンがクリックされた時に必要な処理をcreateTaskに実装していきます。

inputに入力された内容を取得

次に入力フォームに入力された内容を取得する処理を実装して行きたいと思います。

まずはtasks.tsのTasksクラス内に下記のコードを追加してください。

tasks.ts

  ...
  // task-nameのidがあるinputタグを取得
  private inputName = document.getElementById("task-name") as HTMLInputElement

  // task-detailのidがあるinputタグを取得
  private inputDetail = document.getElementById(
    "task-detail"
  ) as HTMLInputElement
  ...

TSではJSと違ってdocument.getElementByIdで入力フォームの値を取得するためには最後にas HTMLInputElementを付けて型を変換する必要があります。

もっと詳しく知りたい方はType Assertionで検索してみてください。

参考記事

次にcreateTask内に入力フォームに入力した内容を取得した処理を書いていきます。

createTaskの中身を下記コードに書き換えてください。

tasks.ts

public createTask() {
  // 入力フォームの内容を取得
  const name = this.inputName.value
  const detail = this.inputDetail.value
  // 動作確認のためalert()を追加
  alert(name)
  alert(detail)
  ...
}

フォームに文字を入力した後に登録ボタンを押して

入力内容がアラートされたらOKです。

登録クリックでタスクを追加

次に登録ボタンをクリックしたらテーブルに行が追加されるようにしていきたいと思います。

※ ここからはJSの内容なのでさらっと流していきます。

index.html

  <tbody id="task-list">
  </tbody>

まずはdocument.getElementByIdtbodyを取得します。

tasks.tsのTasksクラスに下記のようにコードを追加してください。

tasks.ts

export class Tasks {
  ...
  private taskList = document.getElementById("task-list")
  ... 
}

次に登録ボタンクリック時にTSでtbody内に下記のように行とカラムが追加されるようにしていきます。

<tbody id="task-list">
  <tr>
    <td>(タスクNo)</td>
    <td>(入力したタスク名)</td>
    <td>(入力したタスク詳細)</td>
    <td><button>完了</button></td>
  </tr>
</tbody>

tasks.tsを下記に書き換えてください。

tasks.ts

export class Tasks {
  // タスクNoを管理するために使用
  public taskNumber = 1

  // task-listのidがあるタグを取得
  private taskList = document.getElementById("task-list")

  // task-nameのidがあるinputタグを取得
  private inputName = document.getElementById("task-name") as HTMLInputElement

  // task-detailのidがあるinputタグを取得
  private inputDetail = document.getElementById(
    "task-detail"
  ) as HTMLInputElement

  public createTask() {
    // 入力フォームの内容を取得
    const name = this.inputName.value
    const detail = this.inputDetail.value

    // 入力が無い場合はアラートを出して処理終了
    if (!name || !detail) {
      alert("入力してください")
      return
    }

    // taskListが存在しない場合は処理終了
    if (!this.taskList) return

    // trタグを作成
    const tr = document.createElement("tr")

    // tdタグの中身を作成
    const td = `
      <td>${this.taskNumber}</td>
      <td>${name}</td>
      <td>${detail}</td>
      <td><button>完了</button></td>
    `

    // 作成したtrタグにtdタグを設定
    tr.innerHTML = td

    // task-listのidがあるタグの末尾にtrタグを追加
    this.taskList.appendChild(tr)

    // タスク番号をインクリメント(加算)
    this.taskNumber++
  }
}

コードを追加出来たらそれぞれタスク名とタスク詳細を入力をクリックしてみてください。

下記の画像のようにタスクを追加する事ができたらOKです!

代替テキスト

完了機能を作成

次にTODOアプリに必須の完了したタスクを削除する機能を作成していきたいと思います!

※ こちらもJSの内容になるのでさらっと流していきます。

tasks.tscreateTaskを下記のように書き換えてください。

tasks.ts

export class Tasks {
  // タスクNoを管理するために使用
  public taskNumber = 1

  // task-listのidがあるタグを取得
  private taskList = document.getElementById("task-list")

  // task-nameのidがあるinputタグを取得
  private inputName = document.getElementById("task-name") as HTMLInputElement

  // task-detailのidがあるinputタグを取得
  private inputDetail = document.getElementById(
    "task-detail"
  ) as HTMLInputElement

  public createTask() {
    // 入力フォームの内容を取得
    const name = this.inputName.value
    const detail = this.inputDetail.value

    // 入力が無い場合はアラートを出して処理終了
    if (!name || !detail) {
      alert("入力してください")
      return
    }

    // taskListが存在しない場合は処理終了
    if (!this.taskList) return

    // trタグを作成
    const tr = document.createElement("tr")

    // タスクごとのidを作成
    const taskId = `task-${this.taskNumber}`

    // trタグにid="(taskId)"をセット
    tr.setAttribute("id", taskId)

    // 完了ボタン用のidを作成
    const deleteId = `delete-${this.taskNumber}`
    // tdタグの中身を作成
    const td = `
      <td>${this.taskNumber}</td>
      <td>${name}</td>
      <td>${detail}</td>
      <td><button id="${deleteId}">完了</button></td>
    `

    // 作成したtrタグにtdタグを設定
    tr.innerHTML = td

    // task-listのidがあるタグの末尾にtrタグを追加
    this.taskList.appendChild(tr)

    // タスク番号をインクリメント(加算)
    this.taskNumber++

    // 作成したボタンを取得
    const deleteButton = document.getElementById(deleteId)

    // deleteButtonが存在しない場合は処理終了
    if (!deleteButton) return

    // deleteButtonがクリックされた時の処理を追加
    deleteButton.onclick = function () {
      // 確認用アラートが出てきて「はい」を選択した時に
      if (window.confirm("本当によろしいですか?"))
        // taskIdを元に要素を特定して削除
        document.getElementById(taskId)?.remove()
    }
  }
}

課題を作成して完了画面を押した時に画像のように確認が出てきて、はいをクリックした時にタスクが削除されたらOKです!

代替テキスト
代替テキスト

おまけ XSS対策

XSSとは?

下記参考

参考記事

簡単に言うと入力フォームやqueryなどにhtmlのタグやscriptのタグを入力されるとセキュリティ的に殺される可能性があると思ってください。

Vue.jsなどのフレームワークを使っている場合は、FWの機能である程度対策してくれますが

JSやTS単体で使用している場合は自身での対策が必要です。

さっき作ったTODOアプリを攻撃してみよう!笑

先ほど作成したTODOアプリですが、このままだと悪意のあるユーザーに攻撃されると簡単にやられてしまいます。

具体的にどこがダメなのかと言うと下記の部分です。

    const td = `
      <td>${this.taskNumber}</td>
      <td>${name}</td>
      <td>${detail}</td>
      <td><button id="${deleteId}">完了</button></td>
    `

もしタスク名の入力フォームにHTMLタグなどを入力されると

${name}の部分に代入され画面の一部として認識されてしまいます。

実際に入力してみましょう!

下記のコードをコピーしてフォームに入力して登録ボタンを押してみてください。

<div>1億円プレゼント!応募する?</div><a href="https://www.google.com/" style="color: #fff;background: #5876a3;padding: 10px 30px;display: inline-block;border-radius: 5px;text-decoration: none;">はい</a><a href="https://www.google.com/" style="color: #fff;background: #767e8b;padding: 10px 30px;display: inline-block;border-radius: 5px;text-decoration: none;">いいえ</a>

画像のように表示されましたか?

代替テキスト

いかにも怪しそうなボタンが出てきましたね笑

今回はボタンを押すとGoogleに飛ぶように設定していますが

悪意のあるサイトのリンクを設定しておけばそのページに飛ばす事ができます。

対策方法

簡単にできる対策方法としサニタイジングと言うものがあります。

tasks.tsの先頭にこちらのコードを貼り付けてください。

こちらは何をしているのかと言うと

HTMLの一部として認識されてしまう特殊文字と言われる

・ &
・ <
・ >
・ "
・ '

これらが入力されていたらただの文字列に変換します。

実際に使ってみましょう、下記のようにコードを書き換えてください。

tasks.ts

  const td = `
    <td>${this.taskNumber}</td>
    <td>${escapeHTML(name)}</td>
    <td>${escapeHTML(detail)}</td>
    <td><button id="${deleteId}">完了</button></td>
  `

これで先ほどのHTMLを入力してみてください。

代替テキスト

画像のように文字列として読み込まれていればOKです。

もしXSSについて知らなかった人はセキュリティ的にやばいWebサイトを作ってします可能性があるので絶対に覚えておいてください!

最後に

最後に作成したTODOアプリをデプロイしてみましょう!

こちらのコマンドを実行してください!

npm run build

コマンドを実行したらdistフォルダの中にbundle.jsと言う名前のファイルが作成されたかと思います。

代替テキスト

こちらのフォルダを使って下記記事を参考にデプロイしてみてください!

デプロイ手順

以上でTS勉強会を終わります!

ぜひ皆さんの個人開発にもTSを取り入れて安全に開発してみてください!

この記事が気に入ったら
フォローしよう

最新情報をお届けします

Twitterでフォローしよう

おすすめの記事