2016年12月15日

TypeScriptでProject Euler #1 「3と5の倍数」

   このエントリーをはてなブックマークに追加 Clip to Evernote
今回から、実際にProject Eulerの問題をTypeScriptで解いていきます。


問題

第1問は、

1000 未満の 3 か 5 の倍数になっている数字の合計を求めよ.

   Project Euler日本語翻訳サイト から引用

というもの。

この問題は、第1問目ということで簡単ですね。


TypeScriptで問題を解く

TypeScriptでProject Euler #0で作成したテンプレートプログラムをフォルダーごとコピーし、新しいプロジェクトを作成し、Solverクラスのexecメソッドを問題に合わせて書き換えます。

作成したTypeScriptのコードは以下の通り。

import * as Utils from './utils';
import Stopwatch = Utils.Stopwatch;

class Solver {
    exec(num: number): number {
        var sum = 0;
        for (var i = 1; i < num; i++) {
            if (i % 3 === 0 || i % 5 === 0)
                sum += i;
        }
        return sum;
    }
}
class Application {
    static run(): void {
        let sw = Stopwatch.startNew();
        try {
            let p = new Solver();
            let n = p.exec(1000);
            console.log(n);
        } finally {
            sw.stop();
            console.log(sw.toString());
        }
    }
}
Application.run();

※Stopwatchクラスについては、「TypeScriptでProject Euler #0」 を見てください。


プログラムを書き終えて

今回のプログラムでは、新たに得たTypeScriptの知識というはほとんどないかな。

TypeScript は、この程度のプログラムだと、C#と同じ感覚で書けますね。僕の中では、比較演算子が === と3つつなげるのが一番の大きな違いといった感じです。

そういえば、execメソッドで、var 使っちゃいましたが、スコープの扱いがC#に近い let を使ったほうがいいですね。次回からは、let使おうと思います。

ちなみに、僕のMacBookAirでは、デバッグコンソールで20ミリ程度で終わるので、この程度のプログラムならば自分にとって分かりやすいコードで書けばいいかなと思います。LINQ使えれば、もっと簡単に書けるんですけどね。まあ仕方ないですね。


答えは皆さんの楽しみのために載せないでおきますね。

解いた答えがあっているかは、本家サイトでアカウント登録して、該当する問題のページを開くと、答えを入力する欄があり、ここに解いた値を入れcheck ボタンを押すことで、答え合わせができるようになっています。

  

Posted by gushwell at 21:30Comments(0)TrackBack(0)

2016年12月13日

TypeScriptでProject Euler #0 はじめに

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

これまで、初めてのTypeScript(1),(2),(3),(4),(5),(6), と書いてきましたが、今回から記事のタイトルを変更します。

Project Euler とは

Project Eulerとは、数学的問題をひたすらプログラムで解いていくというサイトです。 現時点で500以上もの問題が掲載されています。

問題を日本語に訳してあるサイトもあります。 有志の方々が、日本語に訳して下さっているので、とてもありがたいです。

今回使うプログラミング言語は、C#ではなくTypeScript。Visual Studio Code + node.js の環境で動かします。

TypeScriptってどんな言語かを知るのが目的なので、それほど多くの問題を解くことはしないと思いますが、10問程度は解きたいと思います。

僕の貧弱な数学の知識では、そもそもそんなに多くの問題を解けるとは思いませんので、10問くらいがちょうどよいでしょう。

方針

なお、以下のような方針で臨みたいと思います。

速度にも気を配ったコードとする (時間を計測する)

すべての問題は、1分以内で解ける問題だということです。Project Eulerが開始されたのは、2001年ということですから、初期に出題された問題ならば、今のPCで最悪でも数秒程度で答えが求められるっていうことだと思います。 なので目標は10秒以内。速度にも気を配ったコードにしたいと思っています。

再利用可能なものはクラスとして独立させておく。

後で使えそうなロジックは、別クラスとして定義することとします。 これによって、一時的には、記述するコード量が増えてしまいますが、長い目でみれば、メリットがあると思います。

すべての問題に対して、Solverクラスのexecメソッドを実装する。

後述のひな形コードのように、たとえ冗長になったとしても、問題を解くコードは、Solver クラスの exec静的メソッド あるいは インスタンスメソッドとして実装することとします。 もちろん、execメソッドの引数は、その問題ごとに変化します。

なお、複数のやり方で問題を解いた場合は、SolverB、SolverC というクラスを定義し、そこに同じ引数のexecメソッドを実装します。

外部のライブラリは使わないことにする。

これはやってみたいとわからない部分もありますが、基本は、TypeScriptが持つ基本的な機能だけで問題を解きます。

ひな形コード

実は、前回の記事の「初めてのTypeScript (6)」で示したプログラムが、そのひな形コードになってます。 再度示しますね。

まずは、app.tsを示します。execメソッドの部分は、問題ごとに実装が異なるので、ひな形では、引数で得た値を返すだけのコードとしています。

import { Utils } from './utils';
import Stopwatch = Utils.Stopwatch;

class Solver {
    exec(maxnum: number): number {
        return maxnum;
    }
}

class Application {
    static run(): void {
        let sw = Stopwatch.startNew();
        try {
            let p = new Solver();
            let n = p.exec(100000);
            console.log(n);
        } finally {
            sw.stop();
            console.log(sw.toString());
        }
    }
}

Application.run();

次は、utils.ts。

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);
    }
}

このutils.tsは、これから解くすべての問題で利用します。必要ならばここにクラスを追加していくかもしれません。

これで、動くはずですので、F5キーを押して、正しく動作することを確認します。

tsconfig.json, tasks.json, launch.json

説明は省略しますが、Visual Studio Codeの設定ファイルである tsconfig.json, tasks.json, launch.json も載せておきます。
 

■tsconfig.json

{
    "compilerOptions": {
        "target": "es6", 
        "module": "commonjs",
        "sourceMap": true
    }
}

■.vscode/tasks.json

{
  "version": "0.1.0",
  "command": "tsc",
  "isShellCommand": true,
  "args": ["-p", "."],
  "showOutput":"always",
  "problemMatcher": "$tsc"
}

■.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "起動",
      "type": "node",
      "request": "launch",
      "program": "${workspaceRoot}/ProjectEuler.js",
      "stopOnEntry": false,
      "args": [],
      "cwd": "${workspaceRoot}",
      "preLaunchTask": null,
      "runtimeExecutable": null,
      "runtimeArgs": [
        "--nolazy"
      ],
      "env": {
        "NODE_ENV": "development"
      },
      "console":"internalConsole",
      "sourceMaps": false,
      "outDir": null
    },
    {
      "name": "アタッチ",
      "type": "node",
      "request": "attach",
      "port": 5858,
      "address": "localhost",
      "restart": false,
      "sourceMaps": false,
      "outDir": null,
      "localRoot": "${workspaceRoot}",
      "remoteRoot": null
    },
    {
      "name": "プロセスにアタッチ",
      "type": "node",
      "request": "attach",
      "processId": "${command.PickProcess}",
      "port": 5858,
      "sourceMaps": false,
      "outDir": null
    }
  ]
}

次回は、これらのファイル(.ts, .json)があるフォルダーをコピーして利用します。


次のエントリーからは、実際に問題を解いていきます。

ただし、コードは載せますが、答えそのものは載せません。それは皆さんの楽しみのために取っておきましょう。

  
Posted by gushwell at 22:10Comments(0)TrackBack(0)

2016年12月11日

初めてのTypeScript (6) - モジュールのimport/export

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

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


初めてのTypeScript (4) - classをし別ファイルから利用してみる」で挫折したexport/importに再挑戦します。

今度は、前回構築したVisual Studio Code+TypeScriptの環境を使います(mode.jsを使っている)ので、ブラウザのような制限は受けないはずです。

tsconfig.json, tasks.json, launch.jsonは、基本的にそのまま利用したいので、TypeScriptのソースファイルが入っているフォルダごとコピーして、新しいプロジェクトとします。

TypeScriptのnamespaceは使わない

いろいろ試行錯誤した結果ですが、「TypeScriptでnamespaceは使わない」が現時点での僕の結論です。

C#プログラマーからすると、「なんで?」と思うのですが、モジュールの概念が微妙に違うので、TypeScriptにおいては、namespaceは意味がないという結論にたどり着きました。

それと、詳細は割愛しますが、C#の感覚で複数のファイルに同じnamespace名を付けると、importする側では、名前が重複した状態になりビルドできません。 無理やりビルドエラーを取ろうと頑張ると、何のためのnamespaceかが全く分からないカオス状態になります。

ブラウザ環境でもたぶん同じ状況になるのだと思います(すいません、これは確かめていません)。

モジュールのexport

では、「初めてのTypeScript (4)」と同じように、Utils.tsファイルを定義します。

    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);
        }
    }

これを utils.ts というファイル名で保存します。

モジュールのimport

では、このutils.tsをimportしてみます。

import * as Utils from './utils';

こうすると、同一フォルダにある utils.tsをimportしてくれます。

これで、

let sw = Utils.Stopwatch.startNew();

のような記述が可能になります。

Stopwatchの前のUtils.を省略したいならば、

import * as Utils from './utils';
import Stopwatch = Utils.Stopwatch;

のような書けばよいみたいです。ちなみに、最後の行は、次のコードと基本的には同じ意味だと考えてもらって差し支えないかなと思います。

const Stopwatch = Utils.Stopwatch;

メインプログラムを書く

utils.tsに定義してあるクラスを利用するコードを書いてみました。

import * as Utils from './utils';

class Solver {
    exec(maxnum: number): number {
        return maxnum;
    }
}

class Application {
    static run(): void {
        let sw = Utils.Stopwatch.startNew();
        try {
            let p = new Solver();
            let n = p.exec(100000);
            console.log(n);
        } finally {
            sw.stop();
            console.log(sw.toString());
        }
    }
}

Application.run();

これを ProjectEuler.ts というファイルで保存します。

Shift+Command+Bでビルドします。

無事ビルドできました。

launch.json の書き換え。

一か所だけ、launch.json の書き換えが必要です。以下のように起動するプログラム名を変更します。

 "program": "${workspaceRoot}/ProjectEuler.js",

実行

F5キーで実行します。デバッグコンソールに以下のような出力がされ正しく動いていることが確認できました。

node --debug-brk=16838 --nolazy ProjectEuler.js 
Debugger listening on [::]:16838
100000
00:00:00:021

これから

ProjectEulerという名前でピンときた方もいると思いますが、実は、今まで書いてきた記事は、TypeScriptを使ってProject Eulerの問題を解くための準備みたいなものです。

今回示したコードは、TypeScriptを使ってProjectEulerの問題を解くためのひな型コードとなっています。最初は、ブラウザで動作するプログラムでコードを書こうと考えていたのですが、途中方向転換し、Node.js上で動かすこととにしました。

TypeScriptを学習するために題材としてProject Eulerを選んだので、10問程度を解ければよいかなと今は考えています。

ということで、次回からは、TypeScriptでProjectEulerに挑戦していこうと思っています。 といっても、あと少ししたら来年出版予定のC#の本の執筆作業のラストスパートで忙しい時期に突入してしまうので、どうなることやら...

  
Posted by gushwell at 21:43Comments(0)TrackBack(0)

2016年12月09日

初めてのTypeScript (5) - Visual Studio Code(Mac)でTypeScript

   このエントリーをはてなブックマークに追加 Clip to Evernote
これは TypeScript Advent Calendar 2016 の9日目の記事です。 


えー、前回までVisual Studio 2015を使っていたのですが、export/importでつまずいてしまってので、Visual Studio Code + node.jsの環境で、TypeScriptに再挑戦です。

今回は、Macを使っています。といっても、WindowsでもCommandキーをControlキーと読み替えてもらえれば、Windowsでもほとんどそのまま適用できる内容だと追います。

スクリーンショットを撮っていませんが、普通のプログラマーならば、文字だけでも大丈夫かと思います。


インストール

まずは、Visual Studio codeとnode.js をインストールします。この二つはダウンロードして、インストーラを起動するだけなので難しいことはありません。

それぞれのバージョンは以下のとおりです。

Visual Studio Code : 1.7.2
node.js : 6.9.1


次に、TypeScriptをインストールします。ターミナルを起動し、以下のコマンドを入力します。

npm install -g typescript

でも、このインストールで失敗してしまいました。メッセージを読むと、権限が不足しているらしいです。普通のMacのカジュアルユーザの僕には、Mac/Unixについて疎いので対処方法がよくわかりません。
調べたら、以下のようなコマンドを実行すれば良さげ。

sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}

再度、

npm install -g typescript

でインストール。今度は成功しました。TypeScriptのバージョンは、2.0.10 でした。
 

必要なプログラムのインストールが終わったので、Visual Studio Code(以下、VSCode)を起動します。


以降は、以下のページを参考にしながら、試しています。

https://code.visualstudio.com/Docs/languages/typescript


tsconfig.jsonの作成

TypeScriptのプログラムソースファイルを置く任意のフォルダーを開きます。

このフォルダに、tsconfig.jsonというファイルを作成し、以下のような内容を記述します。

{
    "compilerOptions": {
        "target": "ES5",
        "module": "commonjs",
        "sourceMap": true
    }
}


プログラムコードの入力

次に、同じフォルダーに、HelloWorld.ts ファイルを作成します。ここにTypeScriptのコードを書きます。

class Startup {
    public static main(): number {
        console.log('Hello World');
        return 0;
    }
}

Startup.main();


ビルドの構成

次に、Shift+Command+P を押し、コマンドパレットを出します。

Configure までタイプすると、候補が出てくるので「タスクランナーの構成」を選んで、Enterを押します。

すると、またドロップダウンの一覧が表示されますので、「TypeScript - tsconfig.json. 」を選びます。

これにより、以下にようなtasks.json ファイルが、 .vscodeフォルダーに作成されます。

{
     // See http://go.microsoft.com/fwlink/?LinkId=733558
     // for the documentation about the tasks.json format
     "version": "0.1.0",
     "command": "tsc",
     "isShellCommand": true,
     "args": ["-p", "."],
     "showOutput": "silent",
     "problemMatcher": "$tsc"
}


ビルドの実行

Shift+Command+B でビルドします。

問題がなければ、HelloWorld.tsのある同じフォルダに、HelloWorld.jsファイルが作成されます。


デバッグ環境の設定

いよいよ、デバッグです。Shift+Command+Dでデバッグペイン?を表示させます。

VSCodeのデバッグペインの左上に「デバッグ」というボタンがあるので、それをクリックします。
環境の選択の一覧の中から、「node.js」を選択します。
すると、以下のようなlaunch.jsonファイルが、.vscodeフォルダに作成されます。

{
    // Use IntelliSense to learn about possible Node.js debug attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "プログラムの起動",
            "program": "${workspaceRoot}/app.js",
            "cwd": "${workspaceRoot}",
            "outFiles": [],
            "sourceMaps": true
        },
        {
            "type": "node",
            "request": "attach",
            "name": "プロセスに添付",
            "port": 5858,
            "outFiles": [],
            "sourceMaps": true
        }
    ]
}

このファイルのapp.js を HelloWorld.js に書き換えて保存します。


デバッグの実行

再度、デバッグボタンを押します。F5キーを押してもデバッグを開始できます。

VSCodeの下に、デバッグコンソールに結果が表示されます。
デバッグコンソールが表示されない場合は、Shift+Command+Yで表示できます。
 

Visual Studio 2015と同様、ブレークポイントを設定してデバッグすることもできます。


-----

Visual Studio Codeは、Visual Studioと違って、All in One の環境ではないので、いちいち設定ファイルを自分で記述しないといけないのが面倒です。なんでもGUIで設定できるVisual Studioに慣れすぎた体にはちょっと辛いです。数十年前に戻ってしまったようなそんな感じです。あの頃は、環境設定も楽しい時間でしたが、今は面倒だなって思う気持ちのほうが勝ってます...

まあ、なんとか Visual Studio CodeでTypeScriptをビルド+デバッグする環境が整いました。




 

  
Posted by gushwell at 21:18Comments(0)TrackBack(0)

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#プログラマーには難しすぎるということ。


 

  
Posted by gushwell at 22:23Comments(0)TrackBack(0)

2016年12月05日

初めてのTypeScript (3) - knockout.jsを利用してみる

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

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

前回「初めてのTypeScript (2) - jQueryを利用してみる」の続きです。

今度は、jQueryではなく、knockout.jsをTypeScriptで使ってみます。

パッケージのインストール

まずは、NuGetで以下の2つをインストールします。

knockout.js,  knockout.TypeScript.DefinitelyTyped

index.html の定義

htmlをknockout.js用に書き換えます。

<!DOCTYPE html>
<html lang="ja-jp">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script src="Scripts/knockout-3.4.0.js"></script>
    <script src="app.js"></script>
</head>
<body>
    <h2>TypeScript HTML App</h2>
    <div data-bind="text: nowTime"></div>
    <br>
    <button data-bind="click: onClick, text:buttonFace" type="button" >Start</button>
</body>
</html>

jQueryは使いませんので、読み込む JavaScriptは、knockout-3.4.0.js と app.js の2つです。

data-bind="..."
という箇所が、knockout用の記述です。id属性は不要です。

ViewModel の定義

前回のGreeter クラスをもとに、GreeterViewModel クラスを定義します。

class GreeterViewModel {
    private timerToken: number = null;
    buttonFace: KnockoutObservable<string> = ko.observable(null);
    nowTime: KnockoutObservable<string> = ko.observable("");
    onClick = () => {
        if (this.isRunning()) {
            this.stop();
        } else {
            this.start();
        }
    };

    constructor(buttonFace: string) {
        this.buttonFace(buttonFace);
    }

    private nowString(): string {
        return "The time is: " + new Date().toUTCString();
    }

    private isRunning() {
        return this.timerToken !== null;
    }

    private start() {
        this.buttonFace("Stop");
        this.timerToken = setInterval(() => {
            this.nowTime(this.nowString());
        }, 500);
    }

    private stop() {
        clearTimeout(this.timerToken);
        this.timerToken = null;
        this.buttonFace("Start");
    }
}

buttonFace, nowTime, onClick がknockoutでバインドするメンバーになります。このGreeterViewModelクラスの中では、DOMを直接操作している箇所はありません。

このビューモデルは、モデルも兼ねてしまっていますが、そこは大目にみてください。

GreeterViewModelを利用する

では、このGreeterViewModelを利用して、プログラムが動くようにします。

残りのコードを以下に示します。

class Application {
    static run() {
        let greeter = new GreeterViewModel("Start");
        ko.applyBindings(greeter);
    }
}

window.onload = () => {
    Application.run();
}

動かしてみる

では、F5キーを押して動かしてみます。

Startボタンを押すと、時刻が1秒ごとに変化していくのが確認できます。 Stopボタンを押すと、時刻の更新が止まります。

  
Posted by gushwell at 22:27Comments(0)TrackBack(0)

2016年12月04日

初めてのTypeScript (2) - jQueryを利用してみる

   このエントリーをはてなブックマークに追加 Clip to Evernote
これは TypeScript Advent Calendar 2016 の4日目の記事です。

初めてのTypeScript (1) - 新規プロジェクトを作成してみる」の続きです。

今度は、jQueryを利用してみようと思います。


準備

Visual Studio 2015のNuGetで、jQuery と jQuery 用の jquery.TypeScript.DefinitelyTyped をインストールします。 これだけです。

jQueryを使ってみる。

前回のプログラム(Visual Studioが自動生成する「TypeScriptを使用したHTMLアプリケーション」を jQuery を使って書き換えてみました。

class Greeter {
    private element: JQuery;
    private span: JQuery;
    private timerToken: number;

    constructor(element: JQuery) {
        this.element = element;
        this.element.append("The time is: ");
        this.span = $('<span>');
        this.element.append(this.span);
        this.span.text(new Date().toUTCString());
    }

    start() {
        this.timerToken = setInterval(() => this.span.text(new Date().toUTCString()), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }

}

class Application {
    static run() {
        let greeter = new Greeter($('#content'));
        greeter.start();
    }
}

$(() => {
    Application.run();
});

DOM操作の部分を jQuery を使ったコードに書き換えてみました。HTMLElement型の代わりに、JQuery型を利用しています。

ついでに、staticメソッド使えるのかなということで、Application クラスを定義して、run 静的メソッドを定義しています。

次に、jQueryが使えるように、index.htmlにscriptタグを追加します。

<head>
    ...
    <script src="Scripts/jquery-3.1.1.min.js"></script>
    <script src="app.js"></script>
</head>

これで動かしてみます。無事動きました。

ボタンのクリックイベントを利用する

そういえば、Greeterクラスに定義してある stopメソッドって、定義してるだけで使っていないです。 なので、stopメソッドも使ってみます。

start/stop を行うボタンを配置。初期状態は、"Start"を表示します。

<body>
    <h1>TypeScript HTML App</h1>
    <div id="content"></div>
    <br>
    <button id="startstopButton" type="button">Start</button>
</body>

そして、GreeterクラスにisRunningメソッドを追加します。それに伴い、数か所変更。

class Greeter {
    private element: JQuery;
    private span: JQuery;
    private timerToken: number = null;

    constructor(element: JQuery) {
        this.element = element;
        this.element.append("The time is: ");
        this.span = $('<span>');
        this.element.append(this.span);
    }

    isRunning() {
        return this.timerToken !== null;
    }

    start() {
        this.timerToken = setInterval(() => this.span.text(new Date().toUTCString()), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
        this.timerToken = null;
    }

}

Applicationクラスのrunメソッドを以下のように書き替えます。

class Application {
    static run() {
        let greeter = new Greeter($('#content'));
        $('#startstopButton').on('click', (e) => {
            if (greeter.isRunning()) {
                greeter.stop(); $(e.target).text("Start");
            } else {
                greeter.start(); $(e.target).text("Stop");
            }
        });
    }
}

$(() => {
    Application.run();
});

callback関数を書くのに、いちいち function() { } と書かなくて済むのがとても良いです。

jQuery利用しているところは、JQuery型が導入された以外は、JavaScriptの時とほとんど変わらないですね。
 

動かしてみます。問題なく動いてるようです。

  
Posted by gushwell at 21:00Comments(0)TrackBack(0)

2016年12月01日

初めてのTypeScript (1) - 新規プロジェクトを作成してみる

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

ほんのすこし時間ができたので、久しぶりにブログ更新です。

12/4 追記
TypeScript Advent Calendar 2016があるのをこの記事をアップ後に知りました。
1日目が空いていたので、勝手ながら、TypeScript Advent Calendar 2016 の1日目の記事として登録させていただきました。

-----

TypeScript2.0が正式に公開されたとのことで、TypeScriptに興味が沸いてきたので、ちょこっと勉強してみました。 JavaScriptは初心者に毛が生えたレベル。TypeScriptはほとんど知識ゼロなので、もし、おかしな事書いていたら、指摘していただけるとありがたいです。

準備

Get TypeScript のページの "Visual Studio 2015" のリンクをクリックし、言語で Japanese を選択し、「TypeScript for Visual Studio 2015 - 日本語」をインストールします。

ちなみに、Visual Studio 2015 は、Update3 を適用している必要があるようです。

プロジェクトの作成

Visual Studio 2015を使い、新規プロジェクトの作成で、TypeScriptのプロジェクトを作成してみます。 プロジェクトの種類は、「TypeScriptを使用したHTMLアプリケーション」です。

作成されたプロジェクトには、app.ts というファイルが作成されています。これが、TypeScriptのソースファイルですね。

class Greeter {
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;

    constructor(element: HTMLElement) {
        this.element = element;
        this.element.innerHTML += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }

    start() {
        this.timerToken = setInterval(() => this.span.innerHTML = new Date().toUTCString(), 500);
    }

    stop() {
        clearTimeout(this.timerToken);
    }

}

window.onload = () => {
    var el = document.getElementById('content');
    var greeter = new Greeter(el);
    greeter.start();
};

あわせて、index.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="app.js"></script>
</head>
<body>
    <h1>TypeScript HTML App</h1>

    <div id="content"></div>
</body>
</html>

scriptタグで、app.tsから変換されたJavaScriptのファイル app.js を読み込んでいます。 それ以外は、特筆すべき点はない、htmlファイルです。

読み込んでいるスタイルシートapp.cssは、こんな感じ。

body {
    font-family: 'Segoe UI', sans-serif;
}

span {
    font-style: italic;
}

app.tsを読んでみる

app.tsのコードを読んでみます。このコードで特徴的なのは、classを定義していること。それと、=> 使っていること。arrow式/arrow関数って言うらしいです。

C#プログラマには、とてもわかりやすいですね。JavaScriptをあまり知らなくても、何をやっているかがわかります。elementspantimerTokenは、フィールド、start(), stop() がメソッドですね。

C#と違って、変数宣言は、

HTMLElement element;  

ではなくて、

element: HTMLElement;  

なんですね。Pascalみたいです。

それと、コンストラクタは、クラス名ではなく、constructor キーワードを使うんですね。 C#のコードを見ていると、どれがコンストラクタなのか、パッと見にはわからないので、この書き方の方がスマートのように感じます。

早速、F5キーを押して動かしてみます。

おお、無事に動きました。って、何もコードいじってないので当たり前ですね。

ということは、Visual Studioが自動で、JavaScriptに変換してくれているんですね。

TypeScriptは、メンバーの可視性のデフォルトは、public みたいです。ここはC#と大きく異なるところですね。

ちょっと変更

そういえば、privateって使えるのかな? フィールドは、classの外から見る必要はないので、privateにしてみます。

private element: HTMLElement;
private span: HTMLElement;
private timerToken: number;

ビルドも通りました。

Visual Studio のエディタで、greeter. とタイプした時に表示される候補一覧の中には、もう、privateにしたメソッドは出てきません。

さらに、startメソッドの中の、this.timerTokenthis. を取り去ってみました。そしたら、ビルドエラー! C#と違って、this. は省略できないみたいです。

変換後のJavaScriptファイル

最後に、変換された jsファイルを載せておきます。このjs ファイルは、プロジェクトには追加されませんが、tsファイルと同一フォルダにありました。

var Greeter = (function () {
    function Greeter(element) {
        this.element = element;
        this.element.innerHTML += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }
    Greeter.prototype.start = function () {
        var _this = this;
        this.timerToken = setInterval(function () { return _this.span.innerHTML = new Date().toUTCString(); }, 500);
    };
    Greeter.prototype.stop = function () {
        clearTimeout(this.timerToken);
    };
    return Greeter;
}());
window.onload = function () {
    var el = document.getElementById('content');
    var greeter = new Greeter(el);
    greeter.start();
};

なるほど、startなどのメソッドは、prototypeに登録してくれているんですね。継承については特別なコードを書く必要はないということですかね。

JavaScript書く時にいつも混乱するthisですが、TypeScriptのthis とJavaScriptのthisは、違うってのも、このコード読むと分かりますね。

ちょっと驚いたのは、elementtimerTokenというフィールド定義が、JavaScriptのコードからは、なくなってしまっていること。

このあたりがJavaScriptらしさなのかな? C#プログラマの僕にはちょっと気持ち悪いです。

JavaScript と比べると、TypeScriptは行数が多めですが、見た目のごちゃごちゃ感が無くて、かえってすっきりしている気がします。ラムダ式(TypeScriptではアロー式)も使えるし、C#プログラマーにもわかりやすいです。

  
Posted by gushwell at 21:36Comments(0)TrackBack(0)