読者です 読者をやめる 読者になる 読者になる

nprogram’s blog

フロントエンド開発の学習内容を記載していきます。

KnockoutComputedプロパティをJavaScript記載からTypeScript記載に変換!2

TypeScript Knockout Tutorial

はじめに

Knockoutの公式ページ[http://knockoutjs.com/]

のTutorialであるWorking with Lists and CollectionsをTypeScriptを使って記載しています。ソースコードは、GitHubにアップしています。

Visual Studioを使ったデバッグ方法

今回はVisual Studioを用いたデバッグ方法について記載します。 まずは、いつものように、 Knockout Tutorial Working with Lists and Collectionsの JavaScriptソースコードをTypeScriptに書き直します。

<Knockout Tutorial Working with Lists and Collectionsのソースコード

    self.formattedPrice = ko.computed(function() {
        var price = self.meal().price;
        return price ? "$" + price.toFixed(2) : "None";        
    });    
}

<上記ソースコードをTypeScriptに書き直したもの(バグあり)>

    formatterPrice: KnockoutComputed<string> = ko.computed(() => {
        var price = this.meal().price;
        return price ? "$" + price.toFixed(2) : "None";
    }); 
}

これをVisual Studioで実行すると、エラーで止まります。 エラー内容は、未定義またはNULL参照のプロパティ'price'は取得できません。

f:id:nprogram:20160627215739p:plain

これだけ見てもさっぱりわからないので、解析に移ります。 まず、プログラムが止まっている箇所は、var price = this.meal().price;の行です。 このときのthis.mealの値を確認してみましょう。 イミディエイトウィンドウを使います。 [イミディエイト] ウィンドウは、式のデバックと評価、ステートメントの実行、変数値の出力などのために使用します。 なお、デバッグ時にしか、使用できません。

this.mealと入力すると、undefinedと返ってきました。 初回のformatterPriceが呼び出されたときは、this.mealはundefinedのようです・・・。

f:id:nprogram:20160627220513p:plain

今度は、KnockoutComputed型のformatterPriceプロパティが正しく動作しているか確認します。 (this.meal()の中身を確認するために、JSON.stringifyを使用)

    formatterPrice: KnockoutComputed<string> = ko.computed(() => {
        //var price = this.meal().price;
        //return price ? "$" + price.toFixed(2) : "None";
        return JSON.stringify(this.meal());
    });

実行すると、正しく設定が表示されました。

f:id:nprogram:20160627221814p:plain

次は、実行順序を確認します。 下記のように、SeatReservationクラスのインスタンスを生成(new)している箇所と this.mealを初期化しているSeatReservationのコンストラクタと var price = this.meal().price;の行にブレークポイントを仕掛けます。 f:id:nprogram:20160627222223p:plain

この状態で実行すると、なんということでしょう。 SeatReservationクラスのコンストラクタの初期化処理this.meal(item);が実行される前に、 KnockoutComputed型のformatterPriceプロパティの処理が実行されています。 つまり、コンストラクタ内の初期化処理に入る前に、 KnockoutComputed型のformatterPriceプロパティの処理が実行されていることが今回のバグの原因です。

対策

対策としては、this.meal()がundefinedの場合は、"None"で返すのがベストです。 なお、this.mealとするとメソッドそのものを指してしまうので、誤りです。

<上記ソースコードをTypeScriptに書き直したもの(正常動作するもの)>

    formatterPrice: KnockoutComputed<string> = ko.computed(() => {
        if ( this.meal() === undefined ) {
            return "None";
        }
        else {
            var price = this.meal().price;
            return price ? "$" + price.toFixed(2) : "None";
        }
    });

正常に実行された結果は以下のとおり。 なお、Standard(sandwich)のPriceは、ここでは、10ドル(元はNone)に設定しております。

f:id:nprogram:20160627225419p:plain

まとめ

原因不明のバグにぶつかったときには、以下の方法が有効です。

  • デバッグ時に、イミディエイトウィンドウを用いて変数値を出力させる
  • ブレークポイントを複数セットして、処理の実行順序が期待通りか確認する
  • JSON.stringifyメソッドを使用して、変数の中身を表示させる