nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

thisの扱いに注意! [TypeScript][Knockout]

thisの扱いに要注意!!

removeSeatメソッドの箇所で、以下のエラーメッセージが表示。

app.ts:42 Uncaught TypeError: Cannot read property 'remove' of undefined

    removeSeat = (seat: SeatReservation) => {
        this.seats.remove(seat);
    }

f:id:nprogram:20160619222025p:plain

原因

clickイベントに登録されたメソッド(ここでは、removeSeatのこと)のthisは、イベントの発生源のオブジェクトを参照してしまうことが原因でした。

対策

そこで、上記のメソッドをプロパティに置き換え、アロー関数を使って初期化する方法を採用しました。

    removeSeat = (seat: SeatReservation) => {
        this.seats.remove(seat);
    }

このようにすることで、下記コードのように、コンパイルされたJavaScript結果は、 thisを「_this」に退避 していることがわかります。 これにより、clickイベントに登録されたメソッドであっても、thisが期待値どおり「this.seats」を指し示すことができます。

var ReservationsViewModel = (function () {
    function ReservationsViewModel() {
        var _this = this;
        this.seats = ko.observableArray();
        // Non-editable catalog data - would come from the server
        this.availableMeals = [
            { mealName: "Standard (sandwich)", price: 0 },
            { mealName: "Premium (lobster)", price: 34.95 },
            { mealName: "Ultimate (whole zebra)", price: 290 }
        ];
        this.removeSeat = function (seat) {
            _this.seats.remove(seat);
        };
        this.seats.push(new SeatReservation("Steve", this.availableMeals[0]));
        this.seats.push(new SeatReservation("Bert", this.availableMeals[1]));
        this.seats.push(new SeatReservation("Tom", this.availableMeals[2]));
    }
    // Operations
    ReservationsViewModel.prototype.addSeat = function () {
        this.seats.push(new SeatReservation("", this.availableMeals[0]));
    };
    return ReservationsViewModel;
})();
  • ソースコード全文を以下に記載します
/// <reference path="scripts/typings/knockout/knockout.d.ts" />

// Class to represent a row in the seat reservations grid
class SeatReservation{

    meal: KnockoutObservable<availableMeals> = ko.observable<availableMeals>();


    constructor(public name: string, item: availableMeals) {
        this.meal(item);
    }
}

interface availableMeals{
    mealName: string;
    price: number;
}
// Overall viewmodel for this screen, along with initial state
class ReservationsViewModel {

    seats: KnockoutObservableArray<SeatReservation> = ko.observableArray<SeatReservation>();

    // Non-editable catalog data - would come from the server
    availableMeals = [
        { mealName: "Standard (sandwich)", price: 0 },
        { mealName: "Premium (lobster)", price: 34.95 },
        { mealName: "Ultimate (whole zebra)", price: 290 }
    ];

    constructor() {
        this.seats.push(new SeatReservation("Steve", this.availableMeals[0]));
        this.seats.push(new SeatReservation("Bert", this.availableMeals[1]));
        this.seats.push(new SeatReservation("Tom", this.availableMeals[2]));
    }

    // Operations
    addSeat() {
        this.seats.push(new SeatReservation("", this.availableMeals[0]));
    }

    removeSeat = (seat: SeatReservation) => {
        this.seats.remove(seat);
    }
}



window.onload = () => {
    var viewModel = new ReservationsViewModel();
    ko.applyBindings(viewModel);
}
<!DOCTYPE html>

<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>KnockOut And TypeScript</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>Your seat reservations</h2>

    <table>
        <thead>
            <tr>
                <th>Passenger name</th>
                <th>Meal</th>
                <th>Surcharge</th>
                <th></th>
            </tr>
        </thead>
        <tbody data-bind="foreach: seats">
            <tr>
                <td><input data-bind="value: name" /></td>
                <td><select data-bind="options: $root.availableMeals, value: meal, optionsText: 'mealName'"></select></td>
                <td data-bind="text: meal().price"></td>
                <td><a href="#" data-bind="click: $root.removeSeat">Remove</a></td>
            </tr>
        </tbody>
    </table>
    <button data-bind="click: addSeat">Reserve another seat</button>
</body>
</html>

参考サイト

この問題解決には、以下のサイトを拝見させていただき、解決することができました。 本当にありがとうございます。

http://kuroeveryday.blogspot.jp/2015/04/this.html

http://kojs.sukobuto.com/tips/withTypeScript