2016年12月07日

初めてのTypeScript (4) - classを定義し別ファイルから利用してみる

   このエントリーをはてなブックマークに追加 Clip to Evernote

これは TypeScript Advent Calendar 2016 の7日目の記事です。

今度は、複数のファイルに分けての開発に挑戦です。

クラスを定義する

実際の開発になると、複数のファイルに分割して、TypeScriptのコードを書くことになるとおもうんだけど、それってどうやるんだろうか。

試しに、Stopwatchクラスを書いてみます。 スタートとストップの機能だけの単純なものとします。計測のリスタートは考慮しません。

やっぱりC#同様、名前空間を定義しその中にクラスを定義したい。 ということで、定義したのが以下のクラス。

namespace utils {

    export class Stopwatch {

        private startTime: number;
        private stopTime: number;

        static startNew(): Stopwatch {
            let sw = new Stopwatch();
            sw.start();
            return sw;
        }

        start() {
            this.startTime = Date.now();
        }

        stop() {
            this.stopTime = Date.now();
        }

        elapsed(): number {
            return this.stopTime - this.startTime;
        }

        toString(): string {
            let tms = this.elapsed();
            let h = Math.floor(tms / (1000 * 60 * 60));
            let m = Math.floor(tms / (1000 * 60));
            let s = Math.floor(tms / 1000);
            let ms = Math.floor(tms % 1000);
            return this._zeroPadding(h) + ':' + this._zeroPadding(m) + ':' + 
                   this._zeroPadding(s) + ':' + this._zeroPadding(ms, 3);
        }

        private _zeroPadding(n: number, d: number = 2): string {
            let zero = '';
            for (let i = 1; i < d; i++)
                zero += '0';
            return (zero + n).slice(-d);
        }
    }
}

クラスには、publicではなく、exportを付けるようです。

toString()が意外と面倒でした。もっと簡単に書ければいいのにと思います。 javascriptの知識も中途半端なので、こんな、ダサいコードしか書けませんでした。 なんらかのライブラリを使えば、もっとすっきりと書けるのかもしれませんが、今回は、TypeScriptの勉強なので、まあ、これで妥協しておきます。 C#のToString()って、なんて素敵なんだろと改めて思いました。

ついでに、処理結果とかをブラウザで確認できるように、Traceクラスも定義してみました。

    export class Trace {
        static writeLine(s: any): void {
            let el = $('#Trace__');
            if (el[0] === undefined) {
                el = $(document.body).append("<hr><div id='Trace__'></div>");
                el = $('#Trace__');
            }
            el.append(s + "<br>");
        }
    }

この2つのクラスを、utils.ts というファイル名で保存します。

と、ここまで書いて気がついたのですが、namespaceの名前は、PascalCaseで書くのが流儀のようです。camelCaseで書いちゃいました、直すの面倒なので、このままとします。

 

別のソースファイルからクラスを利用する

これを、別のtsファイル(app.ts)から参照してみます。

作成したのは、1からnまでの整数の和を求めるプログラムで、その時間をStopwatchで計測し、 かかった時間と、求めた答えをTraceを使いブラウザに表示しています。

import Stopwatch = utils.Stopwatch;
import Trace = utils.Trace;

namespace ProjectEuler {
    class Problem {
        solve(maxnum: number): number {
            let sum = 0;
            for (let i = 1; i <= maxnum; i++)
                sum += i;
            return sum;
        }
    }

    class Application {
        static run(): void {
            let sw = Stopwatch.startNew();
            try {
                let p = new Problem();
                let n = p.solve(100000);
                Trace.writeLine(n);
            } finally {
                sw.stop();
                Trace.writeLine(sw.toString());
            }
        }
    }

    $(() => {
        $('#solveButton').on('click', () => {
            Application.run();
        });
    });
}

importで、Stopwatchクラスと Traceクラスを名前空間を指定せずに使えるようにしています。

同じディレクトリにあるのならば、C#の using みたいに、

import utils;

だけでOKにできれば、もっと楽なんですけどどね。

ちなみに、HTMLは、こんな感じです。

<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"
            integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
            crossorigin="anonymous"></script>
    <script src="utils.js"></script>
    <script src="app.js"></script>
</head>
<body>
    <h3>Sample App</h3>

    <button type="button" id="solveButton">Solve</button>
</body>
</html>

今回は、NuGetでjQueryをインストールするのではなく、CDNのものを参照するようにしています。 あたりまえだけど、HTMLからも utils.js を参照しています。これがないと、動きません。

では実行してみます。 無事、5000050000 と答えを出してくれました。


もうひとつの書き方

いろいろ調べていたら、さきほど書いた書き方とは別の書き方があるようです。

import * as utils from "./utils"

このようにしてインポートできるようです。

でも、これだとビルドエラーになってしまいます。このあたりが、よくわかっていないのですが、utils.ts で namespaceを書いてあるのがダメっぽいです。

namespaceがダメってのはどう考えてもおかしいので、僕の理解が不足しているからだと思うのですが、やり方わからないので、utils.ts から namespaceを取りました。

なお、利用する側の app.ts も書き換えが必要のようです。

StopwatchやTraceを利用するには、utils.Stopwatch.startNew(); のように、utils. で修飾します。

これで、ビルドド通るようになりました。

これでOK、と思ったら、実行時に

Uncaught ReferenceError: exports is not defined
Uncaught ReferenceError: require is not defined

というJavaScriptのエラーが出てしまいます。ビルドして作成される .js ファイルには、確かに、exports というキーワードらしきものが使われています。このexportsが定義されていないってどういうことよ?

調べてみたら、importとexportによるモジュール定義/利用は、今のブラウザでは使うことができないようです。

Babel とか Browserifyとかを使うと、jsファイルをさらに変換し、ブラウザが実行できるコードにしてくれるらしいのですが、どうしたら良いのか...

Webで検索してみても日本語の情報ほとんどないし、なんか僕には理解できない難しい手順を踏まなくてはいけないようで、さっぱりわかりません(T T)

とりあえず、最初に書いた方法ではうまくいわけですから、良しとしましょう。

今日わかったこと、TypeScriptのモジュール絡みはC#プログラマーには難しすぎるということ。


 



 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/52479558