開発効率向上のためのtypescriptとファイルモジュール化に必要な知識をまとめてみた

typescriptを使用するとどんなメリットがあるの?

前回、こんな記事を書きました。

上記の記事は「フロントエンジニアよく使う環境をとりあえず触ってみよう!」という趣旨で執筆しましたが、これを読んだ読者は次の疑問を感じたかと思います。

(1)この環境にすると、どういうメリットがあるの?

(2)そもそもtypescriptがわからない。あとファイル分割(モジュール化)ってどうすればいいの?

今回はこの2点の疑問を解消すべくこの記事を執筆しました。

ぜひ型定義による「バグがすぐに発見できる恩恵」と、プログラムのモジュール化による保守性・再利用性の高いプログラミングについて体感することができたら幸いです。

この記事では以下内容について順に必要な知識を実践と共に学んでいきます。

おしながき

(1)環境構築
(2)ES6環境でのjavascript のコーディング入門
(3)typescript 入門
(4)モジュール化してファイル分割してみよう
(5)まとめ

では、さっそくgithubからプロジェクトをダウンロードしてください。

(1)環境構築の下準備をしよう

https://github.com/hothukurou/typescript_sample

git ってなんやねん!という方は写真の通り、緑色の「clone or download」を押してもらって、ダウンロードしてください。

ここからは前回記事で説明したことをもう一度説明します。

必要モジュールをinstallする

ダウンロード後は、必要なモジュールをinstallしましょう。といってもコマンド1回打ったら終わります。

vscode を開いたら、上タブにある「ターミナル」をクリックして、新しいターミナルを開いてください。

(windowsなら ctrl+@ linuxならctrl+p がショートカットです)

画面下に文字を打つターミナルが現れました。

このターミナルに以下のコマンドを打ってください。

npm install

package.jsonに書かれたモジュールが一通りinstallされます。

今回はjquery,webpack,typescriptの3種をインストールしました。

 

次にtypescript(ts)をjavascript(js)ファイルに変換します。この行為をトランスパイルといいます。

typescript のトランスパイルをウォッチする

右上の「表示」->コマンドパレットと選択した後、「tasks:run task」を選択して「tsc ウォッチ」を選択してください。

これで、今後tsファイルを編集すると、その度にトランスパイルされたjsファイルが自動生成されるようになります。

実際にtypescriptフォルダを確認すると、jsファイルが生成されていることが確認できます。

tsconfig.jsonにトランスパイル設定を記述しています。今回はrootDirプロパティを./typescriptフォルダに指定しているため、typescriptフォルダ内のtsファイルを常に関ししてjsファイルにトランスパイルします。

次に、ここで生成したjavascriptファイルをwebpackで一つにまとめます。

webpackで一つにまとめる

今回生成したファイルはscript.jsの一つだけですが、このファイルにjqueryライブラリのjsファイルをまとめて一つのjsファイルを生成しましょう。

以下のコマンドを打つと「jsファイルが変更される度にdistフォルダにmain.jsという一つにまとめたファイルが自動生成される」状態になります。

webpack-cli -w

上のコマンドは覚えにくいので簡単なコマンドを追加しました。

package.jsonを見ていただくとscriptsプロパティに以下の内容が書かれております。

"webpack": "webpack-cli -w", # 左の書き方をした時、(key):(value)と呼びます

scriptプロパティに上記のように書いた場合、以下のコマンド「npm run (key)」と書くことで(value)に書かれたコマンドを実行できるようになります。

つまり、以下のコマンドを書くことでwebpackが自動起動するようになります。

npm run webpack

script プロパティは「長くて面倒なコマンドを省略できる点」が便利なので、今後どんどん使っていくとよいでしょう。

動作確認

さて、正常にmain.jsが生成されましたでしょうか。

ここで、確認のためにブラウザでmain.jsを実行してみましょう。

index.htmlにmain.jsを呼び出すhtmlコードを書いておきました。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=500px,user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <script src="dist/main.js"></script>  <!-- ここでmain.jsを呼んでいる -->
    <title>テストページ</title>
</head>
<body>
    <div class="display"> <!-- display クラス名のhtml elementを用意する-->
    </div>
</body>

</html>

一方でscript.tsには以下のコードが書いてあります。

window.onload = function () {
  console.log("ここに書くとconsole上に表示される");
};

window.onload によって、「画面が全部ロードされたらfunction内の処理を開始する」と書いてあります。これはC言語のint main(){} くらいよく使う頻出構文なのでそのまんま覚えてしまいましょう。

window.onload前にjsコードを実行しても画面が生成されておらず、想定した動作が行われないので、このような書き方をします。

console.logはデバックで頻繁に使うコマンドです。デベロッパーツールのコンソールを用いることで中に書いた内容を確認することができます。

index.htmlをクリックしてみましょう。真っ白な画面が表示されたかと思いますが、ここでF12を押してデベロッパーツールを開いて、consoleを見てください。

上記のようにconsoleの中身が表示されれば、無事に動作確認完了です。

以上で環境構築が完了しました。これからscript.tsをいじってプログラミングを勉強していきましょう。

(2)javascript ES2015(ES6)をおさらいしよう

typescript を本格的にはじめる前に、今一度javascriptの機能をおさらいしましょう。

typescriptはjavascriptを理解した上で使う言語なので、しっかり理解しておきましょう。

そもそも、私たちがこれから書くjavascriptはどのようにして処理されるのでしょうか。

「google chrome」とか「safari」のようなブラウザがjsのプログラムを解釈して、命令を処理しています。そして、この解釈方法はブラウザを開発した人たちが考えて作っています。

この時、各ブラウザの開発者が参考にしている「javascriptの記述方法・処理方法」について書かれている仕様書をECMA scriptと呼びます。

https://ja.wikipedia.org/wiki/ECMAScript

この仕様書は年々改定されておりまして、どのバージョンの仕様書に準拠した書き方をするかを考える必要があります。

今回はES2015(ES6)規格で説明します。2015年に標準化された6番目のECMA scriptということです。

現在、IE11以外のほぼ全てのブラウザでこのES6は対応済みなので、 使用しても問題ないと判断しました。

それでは、おさらいしてみましょう。

変数宣言

ES6以前では、変数宣言はvarを使っていましたが、ES6ではvarは非推奨になりました。let const を使いましょう。

var を使うと、「変数の巻き上げ」という現象が発生して意図しないバグが起こりやすくなるからです。(気になる方は調べてみてね。)

さて、const,letの使い分けについて説明します。以下コードをscript.tsに書いてみてください。

window.onload = function () {
  let a = 0; // 再代入可能な変数定義
  const b = 0; // 再代入不可能な変数定義

  a = 2;
  b = 1; // error!
};

aは再代入できましたが、bについては赤波線が引かれてエラーと表示されていますね。このように、constは「二度と変わらない値」を扱うときに重宝します。

typescript導入のメリットの一つに、「上記のようにエラー箇所を表示してくれる機能」があります。

これは型という概念があるからこそできる機能なのです。

型とは「変数・オブジェクトの定義」について書かれたものであり、ここでは「bはconstなのだから、再代入されない」という定義を行っているため、エラーが表示されました。

ところで、なぜvar を使ってはいけないのでしょうか。実はvar は変数の巻き上げと呼ばれる特殊な挙動をするため、「気が付きにくいバグが頻発しやすい」欠陥があったからです。

var を使っていた方はこれからはlet ,const を使うようにしましょう。

 

ちなみに混乱しないコードを書くコツとして「変数はなるべくconstを使うべき」というものがあります。「状態を示す変数」以外はなるべくconstを使いましょう。

関数宣言

以下の4パターンがあります。関数に4パターンも表記方法があるのがjavascriptのヘンなところであります。

以下コードを実際に貼り付けてみましょう。

window.onload = function () {
  // 関数
  function sum1(a, b) {
    return a + b;
  }

  // コンストラクタ関数を使用する(非推奨なので使わなくてもよいです)
  const sum2 = new Function("a", "b", "return (a + b)");

  // 関数を変数に代入する(オススメ!)
  const sum3 = function (a, b) {
    return a + b;
  };

  // アロー関数(ES6から新規追加)
  const sum4 = (a, b) => {
    return a + b;
  };
  console.log(sum1(1, 2));
  console.log(sum2(3, 4));
  console.log(sum3(5, 6));
  console.log(sum3(7, 8));
};

オススメの書き方は関数を変数に代入する方法とアロー関数を使い分ける方法です。

この二つの表記法の違いは「アロー関数はthisで参照が変わらない」点なのですが、thisってなんだ?という方は一旦無視して次に行きましょう。

配列・if文・for文

配列は連続的な要素を記入する時に役に立ちます。

for文と組み合わせて「同じ処理の繰り返しを短く書く」時に使います。

ES6からfor文で配列を確認する時の書き方で「for of文」と「for each文」が使えるようになりました。

その書き方を説明するために、以下のコードを書いてください。

numbersという配列の中身を順に観ていって、1なら!、0なら-を表示します。

window.onload = function () {
  const numbers = [0, 0, 0, 1, 0, 0, 1, 0, 1];

  console.log("普通の配列の書き方");
  for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] === 1) console.log(i + "!");
    else if (numbers[i] === 0) console.log(i + "-");
  }
  console.log("ES6から追加されたfor of 文");
  for (const number of numbers) {
    if (number === 1) console.log("!");
    else if (number === 0) console.log("-");
  }
  console.log("ES6から追加されたforeach文");
  numbers.forEach((number, i) => {
    if (number === 1) console.log(i + "!");
    else if (number === 0) console.log(i + "-");
  });
};

このあたりは、好きな書き方で書くとよいと思います。

ちなみにif文の等式は「==」よりも「===」を使う方が厳密に値が等価であるかわかるため、基本的にはこちらを使うことを推奨します。

テンプレート構文

また、console文に「console.log(i + “!”)」のような変数を含んだ文字列を表示していましたが、この書き方もES6から新しい書き方が追加されました。

テンプレート構文と呼ばれるものです。「”」を「`」に変更すると、${変数名}で文字列結合せずに変数を含んだ文字列を生成することができます。

結構便利なのでこちらも興味があれば使ってみてください。

window.onload = function () {
  const numbers = [0, 0, 0, 1, 0, 0, 1, 0, 1];

  console.log("普通の配列の書き方");
  for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] === 1) console.log(`${i} !`);
    else if (numbers[i] === 0) console.log(`${i} -`);
  }
  console.log("ES6から追加されたfor of 文");
  for (const number of numbers) {
    if (number === 1) console.log(`!`);
    else if (number === 0) console.log(`-`);
  }
  console.log("ES6から追加されたforeach文");
  numbers.forEach((number, i) => {
    if (number === 1) console.log(`${i} !`);
    else if (number === 0) console.log(`${i} -`);
  });
};

連想配列

連想配列は構造体のようなもので「一つの変数に複数のプロパティを追加できるデータ構造」です。

変数の中に入っている要素をプロパティといいます。

window.onload = function () {
  const date = { year: 12, month: 10, day: 0 };
  console.log(date.year);
  console.log(date.month);
  console.log(date.day);
};

連想配列と配列を組み合わせると、たいていのデータ構造を表現することができます。

ゲーム制作でよく使う例は「ノベルデータの格納」でしょうか。

window.onload = function () {
  const novels = [
    { type: "showImg", arg: "部屋の画像を表示" },
    { type: "text", arg: "ここはただの日常ラブコメの世界" },
    { type: "showChara", arg: "妹キャラクタ表示" },
    { type: "text", arg: "「おはよう!」" },
    { type: "text", arg: "妹が俺を起こしにきたようだ" },
    { type: "showImg", arg: "駅前の画像を表示" },
    { type: "text", arg: "昼、駅前に来ていた" },
    { type: "text", arg: "さ~て、ハンバーガー食べるか" },
  ];

  // クリック毎に1つずつ表示するとよいのだが、今回はfor文で順に表示する
  for (const novel of novels) {
    if (novel.type === "text") {
      // ノベルなのでテキスト表示
      console.log(novel.arg);
    } else if (novel.type === "showImg") {
      // 画像表示命令なので画像を表示
      console.log(novel.arg);
    } else if (novel.type === "showChara") {
      // キャラ表示命令なので、キャラを表示
      console.log(novel.arg);
    }
  }
};

連想配列は「プロパティに何が入っているのか知らないと使いにくい」点が使いにくい点だったのですが、typescriptを用いれば「プロパティにどんなものがあるのか」を補完して表示してくれるので、この問題が解消します。

typescriptがあると、「その変数はどんなものなのか」を型定義で済ませることができるので、覚えることが少なくなり、非常に簡単に組めるようになるのです。

enum型

これもES6から追加された要素です。

enum型とは「あらかじめ選択した種類の値しか持たない変数」型です。

先ほどのノベルゲームで言えばnovel.typeは「showImg,text,showCharaの3つしか持たない」ことがわかるので、enum型を使いましょう。

enum型を使うと以下のように記述することができます。

window.onload = function () {
  const NovelType = {
    ShowImg: "showImg",
    ShowChara: "showChara",
    text: "text",
  };
  const novels = [
    { type: NovelType.ShowImg, arg: "部屋の画像を表示" },
    { type: NovelType.text, arg: "ここはただの日常ラブコメの世界" },
    { type: NovelType.ShowChara, arg: "妹キャラクタ表示" },
    { type: NovelType.text, arg: "「おはよう!」" },
    { type: NovelType.text, arg: "妹が俺を起こしにきたようだ" },
    { type: NovelType.ShowImg, arg: "駅前の画像を表示" },
    { type: NovelType.text, arg: "昼、駅前に来ていた" },
    { type: NovelType.text, arg: "さ~て、ハンバーガー食べるか" },
  ];

  // クリック毎に1つずつ表示するとよいのだが、今回はfor文で順に表示する
  for (const novel of novels) {
    if (novel.type === NovelType.text) {
      // ノベルなのでテキスト表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowImg) {
      // 画像表示命令なので画像を表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowChara) {
      // キャラ表示命令なので、キャラを表示
      console.log(novel.arg);
    }
  }
};

enum型はtypescriptを使うことで最大限威力を発揮することができます。

novel.typeの中身がenumのどれかであることを前提に入力補完をしてくれるのです。

typescriptを導入すると「おかしな書き方をしたときに即時にエラーを表示してくれる」機能があるので、非常にバグの少ないコードを書くことができるようになります。

ES6には他にも様々な機能が追加されていますが、一旦これだけ覚えてもらえればよいと思います。

もっと詳しく知りたい方は以下のページを見てください。

・ES2015(ES6) 入門

https://qiita.com/soarflat/items/b251caf9cb59b72beb9b

では、いよいよtypescript独自の機能について話していきたいと思います。

(3)typescript独自の機能を覚える

変数の型を定義する

typescriptは以下のような書き方で変数の型を定義することができます。

これは「この変数に想定していない値が入っていたらエラーを出せ!」と指令する行為です。

変数の型は大きく分けて「number・string・function・null・undefined」がありますが、自分で型を定義することもできます。

ちなみに、「型がわからない時」にはanyを付けることで、「型でエラーチェックを一切行わない」ようにすることもできます。

window.onload = function () {
  const a = 2; // aは数値が入るのでnumber型
  const b: number = 2; // bはnumber型で、数値が代入される
  const c: number = "a"; // error
  const d: string = "a"; // OK!
  let e: any = 2;
  e = "aaa";             // number->string型に変換しても怒られない
};

型定義を行わなくても、最初に代入された値からtypescriptは自動で型推論を行い、対応する型を決めてくれます。(上記のaがnumber型と定義されたのもそのおかげです)

また、union型と呼ばれる「二個以上の型のどれかが代入される変数だよ!」と宣言することもできます。

window.onload = function () {
  let a: number | string = 2; // aは数値と文字列のどちらも入る
  a = "a"; // 数値->文字列入れてもエラーにならない
};

よく使う例は「値が不定(undefined)の時もある変数」を定義する時です。

window.onload = function () {
  let a: number | undefined; // aは数値だが、入らない可能性もある。
  console.log(a); //undefined
  a = 2;
  console.log(a); // 2
};

ちなみに、特定の値しか持たない型も定義することができます。

const a:"この文字しか入らない"="この文字は入る"; //error!
const b:"テスト1"|"テスト2"="テスト2" // union型でテスト1かテスト2しか入らない変数

変数の型について、概念がだんだんわかってきたのではないでしょうか。

各変数ごとに型を決めておくことで、正しくないコードを明確にすることができるのです。

interface

interfaceでは「プロパティを持つ変数の型」を定義することができます。

window.onload = function () {
  interface Monster {
    hp: number;
    power: number;
    name: string;
  }

  const dragon: Monster = {
    hp: 4,
    power: 2,
    name: "ドラゴン",
  };
  console.log(`${dragon.name}の体力は${dragon.hp}で力は${dragon.power}です`);
};

主に連想配列のプロパティを含めた型を定義するときに使います。

enum型定義

ES6で導入されたenumの型を定義することができます。

ES6での書き方をtypescriptの書き方に変更すると先ほど書いたノベルゲームのプログラムは以下の書き方になります。

window.onload = function () {
  enum NovelType {
    ShowImg = "showImg",
    ShowChara = "showChara",
    text = "text",
  }
  interface Novel {
    type: NovelType;
    arg: string;
  }
  const novels = [
    { type: NovelType.ShowImg, arg: "部屋の画像を表示" },
    { type: NovelType.text, arg: "ここはただの日常ラブコメの世界" },
    { type: NovelType.ShowChara, arg: "妹キャラクタ表示" },
    { type: NovelType.text, arg: "「おはよう!」" },
    { type: NovelType.text, arg: "妹が俺を起こしにきたようだ" },
    { type: NovelType.ShowImg, arg: "駅前の画像を表示" },
    { type: NovelType.text, arg: "昼、駅前に来ていた" },
    { type: NovelType.text, arg: "さ~て、ハンバーガー食べるか" },
  ];

  // クリック毎に1つずつ表示するとよいのだが、今回はfor文で順に表示する
  for (const novel of novels) {
    if (novel.type === NovelType.text) {
      // ノベルなのでテキスト表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowImg) {
      // 画像表示命令なので画像を表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowChara) {
      // キャラ表示命令なので、キャラを表示
      console.log(novel.arg);
    }
  }
};

typescriptのenumとES6で標準化されたenumが微妙に異なるのは「ES6が標準化される前にtypescriptが独自でenum型を実装していた」からです。

トランスパイルされればどちらも意図したとおりに動きますが、typescriptなら上記の書き方を使いましょう。

関数

typescriptの型定義が非常に活躍できるのが関数です。

関数の引数、返り値の型を定義することができます。

物は試し、以下のコードを書いてみてください。

window.onload = function () {
  // a,b は数値 返り値も数値
  const sum = function (a: number, b: number): number {
    return a + b;
  };
  console.log(sum(2, 3));
  console.log(sum(2, "3")); //error!
};

以下のように引数に指定した型以外の値をいれるとエラーが表示されます。

これが非常にバグ低減に効果的なのです。

typescriptを使うのなら、ぜひ引数と返り値の型を定義しましょう。

Class

ES6でクラスが正式に標準化されたのですが、jsのclass定義はなんとprivateの概念がない。

typescriptであればprivateの指定ができるし、非常にシンプルにclassを記述することができます。

window.onload = function () {
  class DateManager {
    private day: number;
    private minute: number;
    private hour: number;
    constructor(day: number, minute: number, hour: number) {
      this.day = day;
      this.minute = minute;
      this.hour = hour;
    }
    getDate(): string {
      return `${this.hour}:${this.minute}:${this.day}`;
    }
  }

  const dateManager = new DateManager(2, 3, 4);
  console.log(dateManager.getDate());
};

このClassを使いこなすことができれば、膨大なコードをClassによって分割して非常に可読性の高いコードを書くことができるようになります。

可読性を高くするための一番の方法は「一つのクラス・関数は一つの役割だけ責任を負わせる」ように設計することです。

それでは、次にここで作成した関数・クラスを別ファイルに分割する方法をお伝えします。

これをモジュール化と呼びます。

(4)webpackを用いてクラスをモジュール化する

ここまで理解してはじめてモジュール化ができるようになります。

このモジュール化はwebpackにより「複数のjsファイルを一つに統合」する機能により実現できています。

今回モジュール化するプログラムは下のノベルプログラムです。

window.onload = function () {
  enum NovelType {
    ShowImg = "showImg",
    ShowChara = "showChara",
    text = "text",
  }
  interface Novel {
    type: NovelType;
    arg: string;
  }
  const novels = [
    { type: NovelType.ShowImg, arg: "部屋の画像を表示" },
    { type: NovelType.text, arg: "ここはただの日常ラブコメの世界" },
    { type: NovelType.ShowChara, arg: "妹キャラクタ表示" },
    { type: NovelType.text, arg: "「おはよう!」" },
    { type: NovelType.text, arg: "妹が俺を起こしにきたようだ" },
    { type: NovelType.ShowImg, arg: "駅前の画像を表示" },
    { type: NovelType.text, arg: "昼、駅前に来ていた" },
    { type: NovelType.text, arg: "さ~て、ハンバーガー食べるか" },
  ];

  // クリック毎に1つずつ表示するとよいのだが、今回はfor文で順に表示する
  for (const novel of novels) {
    if (novel.type === NovelType.text) {
      // ノベルなのでテキスト表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowImg) {
      // 画像表示命令なので画像を表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowChara) {
      // キャラ表示命令なので、キャラを表示
      console.log(novel.arg);
    }
  }
};

このプログラムを「作業しやすい形で分割」してみます。たぶんこうするとよいかなと。

・mainファイル(script.ts)

・ノベルアルゴリズムファイル(novel_logic.ts)

・ノベルデータファイル(novel_data.ts)

では、やってみます。

novel_data.tsファイル作成

それでは、novel_data.tsファイルを作成してください。

画面左側のエクスプローラーのファイル作成箇所をフォーカスしてから、右クリックするか右上のファイルボタンを押してファイルを作成してください。

そしてプログラムを書きます。

export class NovelData {
  novels = [
    { type: NovelType.ShowImg, arg: "部屋の画像を表示" },
    { type: NovelType.text, arg: "ここはただの日常ラブコメの世界" },
    { type: NovelType.ShowChara, arg: "妹キャラクタ表示" },
    { type: NovelType.text, arg: "「おはよう!」" },
    { type: NovelType.text, arg: "妹が俺を起こしにきたようだ" },
    { type: NovelType.ShowImg, arg: "駅前の画像を表示" },
    { type: NovelType.text, arg: "昼、駅前に来ていた" },
    { type: NovelType.text, arg: "さ~て、ハンバーガー食べるか" },
  ];
}

export enum NovelType {
  ShowImg = "showImg",
  ShowChara = "showChara",
  text = "text",
}

export interface Novel {
  type: NovelType;
  arg: string;
}

外部から参照する可能性のある型定義やclassにはexportを付けます。

ノベルの情報が詰まったnovelsは今回classとして宣言して、そのクラスで用いる型をその下に追記しました。

これでモジュールは完成です。

次にこのモジュールを外部から読み込んでみましょう。

script.tsを以下のコードにしてください。

import { NovelData, Novel, NovelType } from "./novel_data";

window.onload = function () {
  const novelData = new NovelData();
  // クリック毎に1つずつ表示するとよいのだが、今回はfor文で順に表示する
  for (const novel of novelData.novels) {
    if (novel.type === NovelType.text) {
      // ノベルなのでテキスト表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowImg) {
      // 画像表示命令なので画像を表示
      console.log(novel.arg);
    } else if (novel.type === NovelType.ShowChara) {
      // キャラ表示命令なので、キャラを表示
      console.log(novel.arg);
    }
  }
};

novelDataというnovel_dataで定義したクラスを呼び出し、テキスト表示させています。

外部ファイルを呼び出すときにはimport 文を使用します。対象ファイルでexportしている変数を呼び出すことができます。

無事に外部モジュールからデータを呼び出すことができたら、次はアルゴリズム部分をクラス化しましょう。

novel_logic.ts

novel_logic.tsファイルを作成して、以下のコードを書き込んでください。

import { Novel, NovelType } from "./novel_data";

export class NovelLogic {
  novels: Novel[];
  constructor(novels: Novel[]) {
    this.novels = novels.concat();
  }
  show() {
    // クリック毎に1つずつ表示するとよいのだが、今回はfor文で順に表示する
    for (const novel of this.novels) {
      if (novel.type === NovelType.text) {
        // ノベルなのでテキスト表示
        console.log(novel.arg);
      } else if (novel.type === NovelType.ShowImg) {
        // 画像表示命令なので画像を表示
        console.log(novel.arg);
      } else if (novel.type === NovelType.ShowChara) {
        // キャラ表示命令なので、キャラを表示
        console.log(novel.arg);
      }
    }
  }
}

NovelLogicクラスは最初に作成したnovel_data.tsモジュールで定義した型を利用しているので、importでnovel_dataから型を引っぱっています。

そして、このロジック部分では「ノベルデータ」を読み込む必要があるので、constructor関数を用いてノベルデータを読み込んでいます。

constructor関数は「クラス定義時に渡した引数を読んでくれるメソッド」です。

さて、script.tsに戻って以下のコードを書いてください。

import { NovelData, Novel, NovelType } from "./novel_data";
import { NovelLogic } from "./novel_logic";
window.onload = function () {
  const novelData = new NovelData();
  const novelLogic = new NovelLogic(novelData.novels);
  novelLogic.show();
};

最初に書いたコードがだいぶ短くなりました。

const novelLogic = new NovelLogic(novelData.novels); にてnovelDataを読み込み、novelLogic.show();でノベルを表示します。

無事に動作したか確認しましょう。

上記のように「あるモジュールが他のモジュールデータを利用する時、constructorを用いて値を受け取るようにする」とそのモジュールはどんなデータに依存しているのか明確になるので、データの結びつきが明確になります。

以上で、webpackを用いたモジュール化の解説を終わります。

(5)まとめ

だいぶ内容の濃い記事になりましたが、理解は進みましたか。

今回学んだ内容を再度まとめます。

・node.js+typescript+webpackの環境構築

・ES6の書き方

・typescriptの型定義方法

・モジュール化によるファイル分割法

各項目についてさらっと説明した内容になりますので、より詳細を知りたい方はググってみてもいいですし、実際の開発に役立てていただけると幸いです。

次は、ゲームライブラリenchant.jsの使い方について説明します。

【宣伝】ブラウザゲーム作りたいけど、何からやってよいかわからない!という方へ

有償で指導承りますので、興味ある方は以下メールアドレスにご連絡ください。

何事も最初は人に教えてもらった方が理解もはかどると思いますので、もし興味がありましたらご相談ください。

tukuchauojisan@gmail.com