KnockoutJs

Сегодня решил покрутить в руках следующий js-фреймворк – Knockout.JS и отметить его различия с другими. Он, как выяснилось, реализует Model-View-ViewModel шаблон проектирования. В данном шаблоне есть следующие составляющие:

  • Модель. Сущность (данные), как и в MVC
  • Представление. Это графический интерфейс
  • Модель представления (ViewModel). Связующее звено между Представлением и Моделью. Представление подписано на события ViewModel, а сама ViewModel содержит методы, с помощью, которых Представление может влиять на Модель.

Для того, чтобы пощупать Knoickout у них на сайте есть прекрасный интерактивный учебник. По нему очень удобно можно шаг за шагом освоить принципы построения приложения на данном фреймворке.

Классический пример, который мы видели в статье про Angular.JS будет выглядеть Knockout так:

<!-- This is a *view* -->

<p>Your name: <input data-bind="value: yourName" /></p>
<p>Hello, <strong data-bind="text: yourName"></strong></p>
// This is a simple *viewmodel*
function AppViewModel() {
    this.yourName = ko.observable("Tester");
}

// Activates knockout.js
ko.applyBindings(new AppViewModel());
Пример на KnockoutJs

Для связывания поля ввода с данными, как здесь видно, используется ko.observable. Если бы не было вызова данной конструкции, то связи бы не состоялось.

Так же видно, что для связывания во всю используются html-атрибуты, что делает это немного похожим на Angular.JS. Используются валидные атрибуты “data-“, с которыми, кстати говоря Angular тоже умеет работать.

А вот что меня несколько удивило, так это то, что для client-side навигации (одностраничного роутинга) предлагают использовать стороннюю библиотеку. В интерактивном учебнике – sammy.  Это наводит на мысль, что встроенных средств для данной цели у фреймворка нет. Но, с другой стороны, зачем изобретать велосипеды?

Очень наглядным в плане быстроты разработки мне показался пример с реализацией GUI для почты.

Код получился совсем небольшим.

script src="/scripts/lib/sammy.js" type="text/javascript"></script>

<!-- Folders -->
<ul class="folders" data-bind="foreach: folders">
    <li data-bind="text: $data, 
                   css: { selected: $data == $root.chosenFolderId() },
                   click: $root.goToFolder"></li>
</ul>

<!-- Mails grid -->
<table class="mails" data-bind="with: chosenFolderData">
    <thead><tr><th>From</th><th>To</th><th>Subject</th><th>Date</th></tr></thead>
    <tbody data-bind="foreach: mails">
        <tr data-bind="click: $root.goToMail">
            <td data-bind="text: from"></td>
            <td data-bind="text: to"></td>
            <td data-bind="text: subject"></td>
            <td data-bind="text: date"></td>
        </tr>     
    </tbody>
</table>

<!-- Chosen mail -->
<div class="viewMail" data-bind="with: chosenMailData">
    <div class="mailInfo">
        <h1 data-bind="text: subject"></h1>
        <p><label>From</label>: <span data-bind="text: from"></span></p>
        <p><label>To</label>: <span data-bind="text: to"></span></p>
        <p><label>Date</label>: <span data-bind="text: date"></span></p>
    </div>
    <p class="message" data-bind="html: messageContent" />
</div>
function WebmailViewModel() {
    // Data
    var self = this;
    self.folders = ['Inbox', 'Archive', 'Sent', 'Spam'];
    self.chosenFolderId = ko.observable();
    self.chosenFolderData = ko.observable();
    self.chosenMailData = ko.observable();

    // Behaviours    
    self.goToFolder = function(folder) { location.hash = folder };
    self.goToMail = function(mail) { location.hash = mail.folder + '/' + mail.id };

    // Client-side routes    
    Sammy(function() {
        this.get('#:folder', function() {
            self.chosenFolderId(this.params.folder);
            self.chosenMailData(null);
            $.get("/mail", { folder: this.params.folder }, self.chosenFolderData);
        });

        this.get('#:folder/:mailId', function() {
            self.chosenFolderId(this.params.folder);
            self.chosenFolderData(null);
            $.get("/mail", { mailId: this.params.mailId }, self.chosenMailData);
        });
    
        this.get('', function() { this.app.runRoute('get', '#Inbox') });
    }).run();    
};

ko.applyBindings(new WebmailViewModel());

Правда ведь совсем не много кода? Помимо этого, он структурирован и понятен. Можно себе представить какую кашу на jquery можно было бы здесь навелосипедить. В данной статье можно было бы разобрать данный код, но лучше, я думаю, воспользоваться интерактивным учебником. Так как мне не хотелось бы кого-то повторять, а хотелось бы нести что-то новое.

Как заметить пройдясь по учебнику, порог вхождения в KnockoutJs довольно низкий. И в этом его большой плюс.

Имеется и свой инструмент отладки. Расширение для Хрома Knockoutjs context debugger, которое добавляем боковую панель с knockout-описанием DOM-элементов.

Существует поддерживаемый Knockout-плагин для валидации форм – Knockout-Validation.

Инструмента для какой либо генерации кода через CLI я не нашел. Да и пожалуй, фреймворк пока не так тяжел, чтобы обзаводиться такими вещами.

Интересным вопросом для меня стало сравнение Knockout и Angular. Как можно заметить по первому примерку кода в данной статье, связывание в Knockout производится иначе чем в Angular. В последнем хватает лишь директив, а в Knockout нужно заворачивать необходимые переменные в конструкцию ko.observable().

Различие не только в синтаксисе. Внутренние механизмы проверки нужности обновления представления или данных коренным образом различаются.

В Knockout представление отреагирует на изменение какой-либо одной переменной сразу же (так как сработает обработчик события). При этом проблемы возникают, если данных меняется слишком много. Это вызовет большую серию обновлений отображения. Но для чего нам все эти промежуточные обновления, если нам интересен только итоговый вид? Это только нагрузит наш процессор.

Так же проблема возникает, если у нас есть зависимые данные. Изменив одну переменную мы изменим другую и это может остаться не замеченным. В Knockout есть специальный механизм dependency tracking, который служит для предотвращения таких ситуаций.

AngularJs же работает по принципу dirty checking  (проверка на измененность данных). Эта проверка производится только когда данные могли измениться. Проверяются сразу все данные и отображаются после полной проверки. Здесь мы не имеем бешеного числа ненужных обновлений отображения, а так же не имеем проблемы с отображением зависимых данных.

Из этого можно заключить, что Knockout будет хорош в небольших (не сильно сложных) приложениях. В то время, как AngularJS будет полезен в приложении со сложной структурой данных и относительно большим их объемом. Свою роль здесь играет так же гораздо более высокий порог вхождения в Angular нежели в Knockout.

Вот пока и все, что хотелось сказать. В следующих статьях я продолжу знакомства с JS-фреймворками.

Добавлено 2017-06-03: Сейчас уже KnockoutJs можно считать устаревшим. За последние 2 года выходили только минорные исправления. В то время как ряд других JS фреймворков бурно развиваются и увеличивают размер сообщества. Поэтому, если вашей целью не стоит поддержка старого проекта на данном фреймворке, то лучше обратить свой взор скажем в сторону Vue.js.

Оставить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *