Создание и структурирование ViewModels

ViewModel в Knockout.js представляет собой объект, который инкапсулирует данные и поведение пользовательского интерфейса. Он служит посредником между моделью данных и представлением, обеспечивая двустороннюю привязку (two-way data binding). В отличие от традиционного подхода, где состояние интерфейса хранится напрямую в DOM, ViewModel позволяет управлять состоянием через JavaScript, что упрощает тестирование и поддержку кода.

Создание простого ViewModel выглядит следующим образом:

function AppViewModel() {
    this.firstName = ko.observable("Иван");
    this.lastName = ko.observable("Иванов");
    this.fullName = ko.computed(() => {
        return this.firstName() + " " + this.lastName();
    });
}

ko.applyBindings(new AppViewModel());
  • observable — специальный объект Knockout.js, который отслеживает изменения значения и автоматически обновляет привязанные элементы интерфейса.
  • computed — вычисляемое свойство, которое автоматически пересчитывается при изменении зависимых observables.

Структурирование больших ViewModel

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

function AddressViewModel(street, city) {
    this.street = ko.observable(street);
    this.city = ko.observable(city);
}

function PersonViewModel(firstName, lastName, address) {
    this.firstName = ko.observable(firstName);
    this.lastName = ko.observable(lastName);
    this.address = address;

    this.fullName = ko.computed(() => {
        return this.firstName() + " " + this.lastName();
    });
}

const address = new AddressViewModel("Ленина", "Москва");
const person = new PersonViewModel("Иван", "Иванов", address);

ko.applyBindings(person);

Ключевые моменты:

  • Вложенные ViewModel позволяют разбивать интерфейс на логические блоки.
  • Можно создавать коллекции объектов с помощью observableArray, что удобно для списков и таблиц:
function AppViewModel() {
    this.people = ko.observableArray([
        new PersonViewModel("Иван", "Иванов", new AddressViewModel("Ленина", "Москва")),
        new PersonViewModel("Мария", "Петрова", new AddressViewModel("Садовая", "Санкт-Петербург"))
    ]);

    this.addPerson = () => {
        this.people.push(new PersonViewModel("Новый", "Пользователь", new AddressViewModel("", "")));
    };
}

Использование подписок (subscriptions)

Knockout.js позволяет реагировать на изменения observables с помощью подписок. Это полезно для выполнения побочных эффектов при изменении данных:

this.firstName.subscribe((newValue) => {
    console.log("Имя изменилось на:", newValue);
});

Подписки могут применяться как к одиночным observables, так и к computed свойствам.

Работа с асинхронными данными

ViewModel часто взаимодействует с внешними источниками данных через AJAX или fetch. Для интеграции асинхронных данных используется комбинация observable и промисов:

function AppViewModel() {
    this.users = ko.observableArray([]);

    fetch("https://api.example.com/users")
        .then(response => response.json())
        .then(data => {
            const userModels = data.map(user => new PersonViewModel(user.firstName, user.lastName, new AddressViewModel(user.street, user.city)));
            this.users(userModels);
        });
}

Преимущества такого подхода:

  • Двусторонняя привязка автоматически обновляет интерфейс при получении данных.
  • ViewModel остается чистым и независимым от конкретной реализации UI.

Применение компонентов для крупных ViewModel

Knockout.js поддерживает компонентный подход. Компоненты позволяют инкапсулировать шаблон и ViewModel:

ko.components.register('person-card', {
    viewModel: function(params) {
        this.person = params.person;
    },
    template: `
        <div>
            <h2 data-bind="text: person.fullName"></h2>
            <p data-bind="text: person.address.street + ', ' + person.address.city"></p>
        </div>
    `
});

Использование компонента в основном шаблоне:

<div data-bind="foreach: people">
    <person-card params="person: $data"></person-card>
</div>

Преимущества компонентов:

  • Повторное использование кода.
  • Изоляция состояния и логики.
  • Легкость тестирования отдельных блоков интерфейса.

Итоговое структурирование

Эффективный подход к построению ViewModel включает:

  • Разделение логики на модули и компоненты.
  • Использование observable и computed для отслеживания состояния.
  • Применение observableArray для динамических списков.
  • Подписки для побочных эффектов.
  • Асинхронное наполнение данных через fetch или AJAX.
  • Компонентный подход для повторного использования шаблонов и логики.

Такой подход обеспечивает поддерживаемость, тестируемость и масштабируемость приложения на Knockout.js.