Подтвердить что ты не робот

TypeScript и привязка нокаута к проблеме 'this' - нужна функция лямбда?

Я создавал функцию htmlHelper с помощью TypeScript и KnockoutJS для редактирования списка писем.

Список электронных писем представляет собой нокаут ObservableArray, называемый email, и у меня есть ссылка на каждый элемент, чтобы удалить их. Это фрагмент 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, это метод в модели view:

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

Это все работает до тех пор, пока не будет выполнен метод deleteItem. "this" в этом методе, когда он вызывается, является элементом в массиве, а не моделью представления. Следовательно this.emails является пустой ссылкой и не работает.

Я знаю, что TypeScript поддерживает синтаксис Lambda, но я не могу найти правильный способ написать это (там несколько примеров).

Или есть другой подход, который я мог бы предпринять?

4b9b3361

Ответ 1

Вы можете получить правильное закрытие для '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);
        }
    }        
}

UPDATE:

Похоже, что с Typescript ver 0.9.1 вы можете добиться того же результата, используя инициализаторы лямбда-поля:

class VM {
    public deleteItem = (emailToDelete: string) => {
        this.emails.remove(emailToDelete);
    }        
}

Ответ 2

Перчатки! Просто привяжите $parent как это:

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

Ответ 3

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

Вам может понадобиться polyfill для 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;
    };
}

Ответ 4

Мое окончательное решение - это базовый класс, который перегружает все прототипные функции себе на конструкторе. Очень похоже на решение Маркуса Гардерота.

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

Преимущества:

  • Все подклассы вынуждены вызывать суперконструктор, который я хотел бы выполнить.
  • Когда выполняется код обратной последовательности, в объекте есть только прототипные функции (переменные добавляются позже).
  • Это позволяет избежать создания больших функций для каждого объекта. Только небольшая прокси-функция создается для каждого объекта при вызове bind.
  • Улучшена организация кода класса, не помещая функции в конструктор.
  • Любая функция может использоваться как обратный вызов, вам не нужно менять код при вызове функции из события.
  • У вас нет риска связывания функций дважды.
  • Лучше привязать функцию только один раз, вместо этого делать это в представлении каждый раз, когда выполняется привязка клика/события.

PS:
Вам все равно понадобится привязка polyfill.
Я использую typesript 0.9.5

Ответ 5

чтобы добавить мои 2 цента, также грязный способ, который использует переменную _this, созданную компилятором Typescript, чтобы сохранить ссылку на это:

public deleteItem(emailToDelete: string) {
    var that = eval('_this');
    // remove item from list
    that.emails.remove(emailToDelete); // remove? in JS,  really? 
}

Ответ 6

Хотя я предпочитаю решение 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);

Ответ 7

Я был вдохновлен ответом bind и придумал это, я думаю, это немного легче читать.

<a href="#" data-bind="click: function () {$parent.deleteItem()}">Delete</a>

Оберните метод в функцию лямбда/анонимности. Не забывайте().

Ответ 8

Использовать привязку данных, например:

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

Назначьте this в that, как показано ниже

public deleteItem(itemToDelete) 
{
    var that = this;
    // remove item from list
    that.emails.remove(itemToDelete); 
}