34

TypeScriptとKnockoutJSを使用してhtmlHelper関数を作成し、メールのリストを編集してきました。

電子メールのリストは、電子メールと呼ばれるKnockout ObservableArrayであり、各アイテムに対するリンクを使用してそれらを削除します。これはHTMLフラグメントです:

<ul data-bind="foreach: emails" >
    <li>
        <a href="#" data-bind="click: $parent.deleteItem">Delete</a>
        &nbsp;<span data-bind="text: $data"></span>
    </li>
</ul>

削除リンクは$parent.deleteItemにバインドされています。これはビューモデルのメソッドです。

// remove item
public deleteItem(emailToDelete: string) {
    // remove item from list
    this.emails.remove(emailToDelete);
}

これはすべて、deleteItemメソッドが実行されるまで機能します。このメソッドが呼び出されたときの「this」は、配列内のアイテムであり、ビューモデルではありません。したがって、this.emailsはnull参照であり、失敗します。

TypeScriptがLambda構文をサポートしていることは知っていますが、これを記述する正しい方法が見つかりません(例はほとんどありません)。

それとも私が取ることができる別のアプローチがありますか?

4

8 に答える 8

49

クラスコンストラクター内でメソッド本体を宣言することにより、「this」の正しいクロージャーを取得できます

class VM {
    public deleteItem: (emailToDelete: string) => void;

    constructor() {
        this.deleteItem = (emailToDelete: string) => {
            // 'this' will be pointing to 'this' from constructor
            // no matter from where this method will be called
            this.emails.remove(emailToDelete);
        }
    }        
}

アップデート:

Typescript バージョン 0.9.1 以降、ラムダ フィールド初期化子を使用して同じ結果を得ることができるようです。

class VM {
    public deleteItem = (emailToDelete: string) => {
        this.emails.remove(emailToDelete);
    }        
}
于 2012-11-08T08:45:22.527 に答える
6
declare class Email { }
declare class ObservableArray {
    remove(any): void;
}

class MyViewModel {
    public emails : ObservableArray;

    constructor() {
        Rebind(this);
    }

    public deleteItem(emailToDelete: Email) {
        this.emails.remove(emailToDelete);
    }
}

function Rebind(obj : any)
{
    var prototype = <Object>obj.constructor.prototype;
    for (var name in prototype) {
        if (!obj.hasOwnProperty(name)
                && typeof prototype[name] === "function") {
            var method = <Function>prototype[name];
            obj[name] = method.bind(obj);
        }
    }
}

次のポリフィルが必要な場合がありFunction.bind()ます。

// Polyfill for Function.bind(). Slightly modified version of
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (typeof Function.prototype.bind !== "function") {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = <any[]> Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {},
            fBound = function() {
                return fToBind.apply(this instanceof fNOP && oThis ? this: oThis, aArgs.concat());
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}
于 2012-11-08T08:44:57.643 に答える
6

私の最終的な解決策は、コンストラクターですべてのプロトタイプ関数をそれ自体に再バインドする基本クラスです。Markus Jarderot のソリューションによく似ています。

class BaseClass {
    constructor() {
        for (var i in this) {
            if (!this.hasOwnProperty(i) && typeof (this[i]) === 'function' && i != 'constructor') {
                this[i] = this[i].bind(this);
            }
        }
    }
}

利点:

  • すべてのサブクラスは、私が望んでいた動作であるスーパー コンストラクターを呼び出さなければなりません。
  • rebind コードが実行されると、オブジェクトにはプロトタイプ関数しかありません (変数は後で追加されます)。
  • すべてのオブジェクトで大きな関数を作成することを回避します。bind を呼び出すと、オブジェクトごとに小さなプロキシ関数のみが作成されます。
  • コンストラクターに関数を配置しないことで、クラス コードの構成を改善します。
  • 任意の関数をコールバックとして使用できます。関数がイベントから呼び出されるときにコードを変更する必要はありません。
  • 関数を 2 回バインドするリスクはありません。
  • クリック/イベント バインドが実行されるたびにビューで実行するのではなく、関数を 1 回だけバインドすることをお勧めします。

PS:
引き続きバインド ポリフィルが必要です。
私はtypesript 0.9.5を使用しています

于 2013-12-11T01:08:09.633 に答える
2

私の2セントを追加するには、Typescriptコンパイラによって作成された変数 _this を利用して this への参照を保持する汚い方法もあります:

public deleteItem(emailToDelete: string) {
    var that = eval('_this');
    // remove item from list
    that.emails.remove(emailToDelete); // remove? in JS,  really? 
}
于 2014-12-02T13:04:25.803 に答える
1

次のようなデータバインドを使用します。

data-bind="click:$parent.deleteItem.bind($parent)"

this以下にthat示すように割り当てます

public deleteItem(itemToDelete) 
{
    var that = this;
    // remove item from list
    that.emails.remove(itemToDelete); 
}
于 2016-02-09T01:49:50.687 に答える
0

私は Markus のソリューションを好みますが、この問題を回避するために以前に使用したものを次に示します。

public fixThis(_this, func) {
    return function () {
        return _this[func].apply(_this, arguments);
    };
}

<a href="#" data-bind="click: fixThis($parent, 'deleteItem')">Delete</a>

メソッド名の後に引数を追加することで、追加の引数をメソッドに渡すことができることに注意してください。

fixThis($parent, 'deleteItem', arg1, arg2);
于 2012-12-27T22:48:10.610 に答える