01.01.2018

Как работает отслеживание зависимостей — Knockout.js

Новичкам не обязательно это знать, но более продвинутые разработчики захотят узнать, почему мы продолжаем делать все эти утверждения о том, что KO автоматически отслеживает зависимости и обновляет правильные участки UI…

На самом деле, все довольно просто и красиво. Алгоритм отслеживания работает наподобие такого:

  1. Когда вы объявляете computed переменную, KO мгновенно вызывает функцию, переданную в качестве параметра (далее функция А), для получения начального значения.
  2. В то время, как эта функция запущена, KO настраивает подписку на любые observable-переменные (включая другие computed переменные), которые читает данная функция. Callback-функция подписки настроена так, чтобы повторно запускать «функцию А», повторяя весь процесс с шага 1 (убирая все старые подписки, которые больше не применяются).
  3. KO уведомляет любых подписчиков о новом значении вашей computed переменной.

Итак, Knockout не просто отслеживает зависимости при первом запуске  переданной в computed-переменную функции — он повторно смотрит на зависимости каждый раз. Например, это значит, что зависимости могут динамически изменяться: зависимость А может определять, будет ли computed переменная так же зависеть от Б или В. Затем, функция будет повторно запущена, когда либо А или текущий выбор из Б или В изменился. Вам не нужно специально объявлять зависимости: они определяются во время работы скрипта из выполнения кода.

Другой ловкий трюк заключается в том, что декларативные биндинги реализованы как computed переменные. Таким образом, если биндинг читает значение observable-переменной, этот биндинг становится зависимым от этой observable-переменной, что заставляет этот биндинг повторно выполняться, если observable-переменная изменилась.

Pure computed-переменные работают немного иначе. Для более подробной информации, смотри документацию для purecomputed переменных.

Контролируем зависимости используя peek

Обычно система автоматического отслеживания зависимостей в Knockout делает именно то, что вам нужно. Но иногда может быть так, что нужно контролировать, какие из observable-переменных будут обновлять вашу computed-переменную, особенно, если computed-переменная делает какого-то рода действия, вроде выполнения Ajax запроса. Функция peek позволяет получить доступ к observable или computed переменной без создания зависимости.

В примере ниже, computed-переменная используется для перезагрузки observable-переменной с именем currentPageData используя Ajax с данными из двух других observable-свойств. Computed переменная будет обновляться, когда изменилась переменная pageIndex, но будет игнорировать изменения в selectedItem потому что доступ к её значению производится через функцию peek. В этом случае, пользователь может захотеть использовать текущее значение selectedItem только для отслеживания назначения данного запроса, когда новый набор данных был загружен.

ko.computed(function() {
    var params = {
        page: this.pageIndex(),
        selected: this.selectedItem.peek()
    };
    $.getJSON('/Some/Json/Service', params, this.currentPageData);
 }, this);

Примечание: Если вам просто хочется избавиться от слишком частого обновления computed-переменной, смотри экстендер rateLimit.

Игнорирование зависимостей внутри computed

Функция ko.ignoreDependencies доступна для сценариев, где вам нужно выполнить код внутри computed-переменной, который не должен рассматриваться как зависимость для данной computed-переменной. Часто такое бывает полезно в произвольных биндингах, когда вы хотите вызвать какой-то код, который может получить доступ к observable-переменным, но при этом не хотите, чтобы биндинг повторно выполнялся, при изминениях в этих observable-переменных.

ko.ignoreDependencies( callback, callbackTarget, callbackArgs );

Пример:

ko.bindingHandlers.myBinding = {
    update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var options = ko.unwrap(valueAccessor());
        var value = ko.unwrap(options.value);
        var afterUpdateHandler = options.afterUpdate;
 
        // разработчик передал функцию, которую нужно вызывать при обновленнии данного биндинга, но
        // мы не очень то хотим отслеживать любые зависимости, которые могут повторно запустить этот биндинг
        if (typeof afterUpdateHandler === "function") {
            ko.ignoreDependencies(afterUpdateHandler, viewModel, [value, color]);
        }
 
        $(element).somePlugin("value", value);
    }
}

Примечание: Почему циклические зависимости бессмысленны

Computed-переменные должны отображать набор входящих observable-переменных в виде одиночной observable-переменной на выходе. Таким образом, нет смысла включать циклы в ваши цепочки зависимостей. Циклы не будут аналогичны рекурсии; они будут аналогичны тому, что у вас есть скажем две ячейки электронной таблицы, значения которых вычиляются функцией, на основе значений друг друга. Это приводит к бесконечному циклу выполнения.

Итак, что же делает Knockout если у вас есть цикл внутри графа зависимостей? Он предотвращает бесконечные циклы путем применения следующего правила: Knockout не перезапустит выполнение расчета computed-переменной когда он уже выполняется. Это вряд ли повлияет на ваш код. Это актуально в двух случаях: когда две computed-переменные зависят друг от друга (возможно торлько, если одна или обе используют опцию deferEvaluation), или когда computed-переменная записывает значение в другую observable-переменную от которой у нее есть зависимость (либо напрямую, либо через цепочку зависимостей). Если вам нужно использовать один из этих шаблонов и вы хотите полностью предотвратить циклическую зависимость, то можете использовать функцию peek описанную выше.


Эксклюзивно для сайта skv1991.ru

Перепечатка и копирование строго запрещены.

Оригинал страницы

Опубликовано: 01/01/2018

Добавить комментарий

Ваш адрес email не будет опубликован.