Статьи

Понимание нокаута

KnockoutJS – это элегантная библиотека JavaScript, основанная на шаблоне Model-View-ViewModel, которая помогает нам легко создавать богатые пользовательские интерфейсы. Если вы разрабатываете приложение с разделами, которые динамически обновляются при изменении базовой модели данных, то Knockout действительно может вам помочь. Двухсторонние функции привязки данных и шаблонов в Knockout делают процесс реализации динамических представлений быстрым. Этот учебник поможет вам начать работу с Knockout и покажет, как использовать его в ваших собственных проектах.

Установка нокаута

Установка Knockout – это включение небольшого файла JavaScript в вашу HTML-страницу. Перейдите на сайт Knockout и загрузите производственную версию. Кроме того, вы можете включить Knockout из CDN. Просто поместите следующий <script> в ваш HTML-документ.

 <script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-min.js'></script> 

MVVM Pattern

Чтобы эффективно использовать Knockout, вы должны сначала понять, что такое шаблон MVVM. Если вы уже понимаете паттерн MVVM, вы можете пропустить следующий раздел.

Модель: M в MVVM обозначает модель, которая обычно является постоянными бизнес-данными приложения. В большинстве случаев вы сначала будете читать эти данные с сервера с помощью Ajax-вызова и отображать их в пользовательском интерфейсе. Например, если вы хотите получить список заметок с сервера, вы можете сделать запрос AJAX GET на сервер.

Представление: в Knockout представление – это просто HTML-страница, которая отображает ViewModels (мы подойдем к этому). Всякий раз, когда эти ViewModel изменяются, также изменяются определенные части представления, связанные с ViewModel.

ViewModel: Проще говоря, ViewModel – это модель, представленная представлением. Это чисто кодовое представление данных и поддерживаемых операций над ними. ViewModel обычно не сохраняется и содержит несохраненные изменения, с которыми работает пользователь. Если вы хотите сохранить изменения позже, вы можете отправить эти данные обратно на сервер. В Knockout ViewModels реализованы с помощью POJO (Plain Old JavaScript Objects). Например, если вы отображаете список заметок todo, то ваша ViewModel может содержать список таких объектов заметок и предоставлять несколько функций для изменения / добавления заметок.

Начиная

В качестве первого шага к изучению Knockout, давайте рассмотрим ViewModels и привязку данных. Следующий фрагмент создает простую ViewModel:

 function NameViewModel() { this.name = 'John Doe'; } 

В качестве альтернативы ViewModel может быть записан как объект, как показано ниже.

 var nameViewModel = { name: 'John Doe' } 

Теперь в HTML вам просто нужно написать следующую декларативную привязку для соединения со свойством name в ViewModel.

 Hello, <span data-bind="text:name"></span> 

Этот фрагмент просто связывает пользовательский интерфейс со свойством name в ViewModel. Здесь значение name является innerHTML в тег span . Теперь, в качестве последнего шага, нам нужно сообщить Knockout, какой ViewModel принадлежит свойство name . Для этого просто добавьте следующий код.

 ko.applyBindings(new NameViewModel()); 

Это заставляет Knockout выполнять привязку данных. В результате в HTML мы видим значение name внутри элемента span .

Примечание. В сложных приложениях у вас может быть несколько моделей ViewModel вместо одного. В этом случае вы можете привязать конкретную ViewModel к определенной части пользовательского интерфейса, передав второй аргумент ko.applyBindings() . Пример этого показан ниже.

 ko.applyBindings(new ContactViewModel(), document.getElementById('contacts-area')); ko.applyBindings(new NoteViewModel(), document.getElementById('notes-area')); 

Последнее, на что следует обратить внимание, это то, что вы не должны вызывать ko.applyBindings() пока документ не будет готов. Если вы используете jQuery, оберните вызов внутри $(document).ready() . В VanillaJS вы можете использовать DOMContentLoaded событий DOMContentLoaded .

Двухстороннее связывание с наблюдаемыми

Мы только что узнали, как связать свойство модели с пользовательским интерфейсом. Тем не менее, мы можем пойти еще дальше и сделать эту вещь динамичной. С помощью observables вы можете привязать свойство к пользовательскому интерфейсу с одним дополнительным преимуществом – при каждом изменении свойства ViewModel пользовательский интерфейс обновляется автоматически. Кроме того, мы можем использовать функцию декларативного связывания Knockout, чтобы свойство ViewModel также обновлялось при изменении значения в пользовательском интерфейсе (например, значения поля ввода). Это синхронизирует ваши ViewModel и View.

Чтобы обновить представление в соответствии со значением свойства, необходимо сделать свойство наблюдаемым. Это довольно просто. Просто измените наш предыдущий код, чтобы он выглядел так:

 function NameViewModel() { this.name = ko.observable(''); //initially empty } 

Теперь наше представление будет обновляться при изменении name свойства. Теперь давайте добавим поле ввода в HTML и свяжем его с name чтобы всякий раз, когда пользователь вводил его, свойство менялось, и мы видели обновленное значение в теге span .

 <input type="text" data-bind="value:name,valueUpdate:'input'" placeholder="start typing a name here"/> Hello, <span data-bind="text:name"></span> 

Здесь мы используем синтаксис декларативного связывания Knockout. В атрибуте data-bind value указывает, к какому свойству мы хотим привязаться. Второй параметр valueUpdate указывает, когда обновлять свойство ViewModel. Поскольку мы установили для него значение 'input' , свойство в ViewModel будет обновляться при каждом изменении значения поля ввода. Чтобы увидеть эту функцию в действии, взгляните на этот поршень.

Вы также можете получать уведомления, когда изменяется любое наблюдаемое значение. В следующем примере показано, как это сделать с помощью функции subscribe() .

 function NameViewModel() { this.name = ko.observable(''); this.name.subscribe(function(newVal) { console.log(newVal); //logs whenever the value changes }); } 

Работа с вычисляемыми наблюдаемыми

Иногда вы можете захотеть использовать производное свойство, значение которого зависит от одного или нескольких других свойств. Если вы отображаете это производное свойство в представлении, логично обновлять его всякий раз, когда свойства зависят от изменения. Для этого нам нужна вычисляемая наблюдаемая, которая создается следующим образом:

 function ContactViewModel() { this.phone = ko.observable(); this.email = ko.observable(); this.contactInfo = ko.computed(function() { return this.phone() + ", " + this.email(); }, this); } 

Теперь в представлении мы можем связать вычисляемую наблюдаемость, используя следующий HTML.

 Contact Information: <span data-bind="text: contactInfo"></span> 

Посмотрев на обратный вызов (или функцию оценки), который вы передаете ko.compute() , Knockout знает, от каких наблюдаемых зависит ваша вычисляемая наблюдаемая. Всякий раз, когда любой из них изменяется, ваша функция оценки вызывается автоматически.

Второй параметр ko.compute() – это объект, который должен использоваться как таковой в вашей функции оценки. Стоит отметить, что вы можете реализовать ту же функциональность, используя замыкания, как показано ниже.

 function ContactViewModel() { var self = this; self.phone = ko.observable(); self.email = ko.observable(); self.contactInfo = ko.computed(function() { return self.phone() + ", " + self.email(); }); } 

Последнее, на что следует обратить внимание, это то, что когда вы делаете свои свойства наблюдаемыми, вы больше не должны обращаться к ним напрямую в своем коде. Вместо этого нам нужно называть их как функции. Вот почему значения phone и email были получены с помощью вызовов функций в предыдущем примере. Чтобы установить значение наблюдаемой, просто передайте новое значение в качестве аргумента функции (т.е. self.phone(4657324573) ).

Наблюдаемые массивы

Всякий раз, когда мы хотим обнаружить изменения в одном свойстве ViewModel, наблюдаемые пути – это путь. Но во многих случаях нам интересно знать, изменилась ли коллекция объектов. В таких случаях мы можем использовать наблюдаемые массивы, которые создаются с использованием следующего кода.

 function NotesViewModel() { this.notes = ko.observableArray(); } 

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

Вы также можете передать начальное значение в ваш наблюдаемый массив следующим образом:

 this.notes = ko.observableArray(['one', 'two', 'three']); 

Вы также можете выполнять различные операции с массивами, такие как pop() , push() , shift() , unshift() , reverse() , sort() , splice() и т. Д. Не так ли?

Простое приложение нокаут

В этом разделе мы создадим приложение Knockout с простым пользовательским интерфейсом для управления данными мобильного телефона. Когда новый сотовый телефон добавлен, детали будут отображаться в таблице. Пользователь также может удалить элемент из таблицы. Взгляните на демонстрацию, которая показывает конечный результат!

Прежде чем идти дальше, убедитесь, что у вас установлены как Knockout, так и подключаемый модуль Knockout mapping. Я расскажу о подключаемом плагине позже. Пока просто включите его после библиотеки Knockout:

 <script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"/> 

Шаг 1

Давайте предположим, что каждый сотовый телефон будет иметь три свойства: name , os и price . Нам нужно создать ViewModel и добавить несколько примеров телефона:

 function PhonesViewModel() { var self = this; self.phones = ko.observableArray([{ name: 'Sony Xperia Z1', os: 'Android', price: 599 }, { name: 'Apple iPhone 5S', os: 'iOS', price: 199 }, { name: 'Google Nexus 5', os: 'Android', price: 299 }]); } 

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

Шаг 2

Создайте объект для хранения текущих данных телефона, вводимых в представлении. Нам нужно добавить следующий код в нашу ViewModel.

 self.currentPhone = ko.mapping.fromJS({ name: '', os: '', price: '' }); 

Объект currentPhone содержит данные телефона, которые вводятся в пользовательском интерфейсе. Мы привязываем свойства name , os и price к полям ввода в HTML. Мы также хотим очистить поля после добавления данных телефона. Чтобы очистить поля, нам нужно сделать эти свойства видимыми и затем очистить их внутри функции, в которую мы добавляем телефон. Функция ko.mapping.fromJS() из плагина отображения Knockout автоматически делает свойства объекта наблюдаемыми, поэтому нам не нужно писать ko.observable() для каждого свойства.

Шаг 3

Далее мы хотим предоставить средство для добавления нового телефона в наш список. Просто добавьте следующую функцию в нашу ViewModel.

 self.addPhone = function() { self.phones.push(ko.mapping.toJS(self.currentPhone)); self.currentPhone.name(''); self.currentPhone.os(''); self.currentPhone.price(''); }; 

ko.mapping.toJS() создает и возвращает объект с обычными свойствами, а не наблюдаемыми. Затем мы добавляем этот объект в наш список телефонов и очищаем свойства currentPhone, чтобы он отражался в представлении.

Шаг 4

На этом шаге мы позволим пользователю удалять телефоны из списка. Это достигается с помощью следующей функции.

 self.removePhone = function() { self.phones.remove(this); }; 

Здесь this представляет конкретную строку в нашей таблице, которая будет удалена.

Шаг 5

Затем добавьте следующую разметку:

 <table> <thead> <tr> <td></td> <th>Name</th> <th>OS</th> <th>Price</th> </tr> </thead> <tbody data-bind="foreach: phones"> <tr> <td><a href="#" data-bind="click: $parent.removePhone">Remove</a></td> <td data-bind="text: name"></td> <td data-bind="text:os"></td> <td data-bind="text:price"></td> </tr> </tbody> </table> <hr/> <h3>Add a new Phone</h3> <form data-bind="submit:addPhone"> <input type="text" data-bind="value:currentPhone.name,valueUpdate:'input'" placeholder="Phone Name" /> <br/> <input type="text" data-bind="value:currentPhone.os,valueUpdate:'input'" placeholder="OS" /> <br/> <input type="text" data-bind="value:currentPhone.price,valueUpdate:'input'" placeholder="Price" /> <br/> <button type="submit">Add</button> </form> 

В этой разметке мы использовали привязку данных foreach которая перебирает список телефонов и отображает их. Мы также использовали привязку по click чтобы удалить элемент из таблицы. Это вызывает removePhone() в нашей ViewModel. Поскольку мы находимся внутри foreach привязки, для нас создан новый контекст. Чтобы получить ссылку на корневой ViewModel, мы используем $parent .

Следующее, что нужно отметить, это обязательная отправка. Это предотвращает обычный процесс отправки формы при нажатии кнопки отправки. Это позволяет нам указать пользовательскую функцию, которая будет вызываться вместо. В этом случае addPhone() вызывается, добавляя новый телефон. Внутри формы у нас есть три поля ввода, которые синхронизируются со свойствами currentPhone . Таким образом, как только кто-то нажимает кнопку отправки, у нас есть данные в объекте currentPhone , который просто нужно отправить в наш список телефонов.

Шаг 6

Активируйте Knockout, используя следующий код, и смотрите, как все работает!

 ko.applyBindings(new PhonesViewModel()); 

Вывод

KnockoutJS, безусловно, является отличной средой для построения динамических интерфейсов с использованием шаблона MVVM. Этот учебник охватывал основы библиотеки, но, безусловно, есть продвинутые концепции, о которых вы должны знать. Следующие ресурсы могут помочь вам:

Полный исходный код для демонстрации этой статьи можно найти на GitHub .