Архитектура приложений на Knockout.js часто строится вокруг компонентов и маршрутов. Компоненты инкапсулируют шаблон, логику и стили, а маршруты управляют тем, какая часть интерфейса отображается в конкретный момент времени. При росте приложения возникает проблема: загрузка всех компонентов сразу увеличивает время старта и объём передаваемого кода. Решением становится ленивая загрузка компонентов в зависимости от текущего маршрута.
Ленивая загрузка означает, что код компонента и его шаблон подгружаются только тогда, когда маршрут активируется. Это особенно важно для SPA с большим количеством экранов.
Knockout.js не содержит встроенного роутера, поэтому обычно используется внешний инструмент:
hashchange или
history APIНезависимо от выбранного решения, общая схема выглядит так:
component или
if.Минимальная модель маршрутизации строится на одном наблюдаемом значении:
function AppViewModel() {
this.currentRoute = ko.observable('home');
}
HTML:
<div data-bind="component: currentRoute"></div>
В этом виде все компоненты должны быть зарегистрированы заранее, что не подходит для ленивой загрузки.
Knockout поддерживает асинхронную регистрацию компонентов через
ko.components.register, где viewModel и
template могут быть загружены динамически.
Пример регистрации компонента с использованием
require.js:
ko.components.register('profile', {
viewModel: {
require: 'components/profile/profile'
},
template: {
require: 'text!components/profile/profile.html'
}
});
Однако даже такая регистрация обычно выполняется при старте приложения. Для ленивой загрузки требуется регистрировать компонент только при первом обращении к маршруту.
Используется промежуточный слой между маршрутом и отображением компонента.
Пример:
function AppViewModel() {
this.currentComponent = ko.observable(null);
this.loadedComponents = {};
}
При смене маршрута:
function loadRoute(routeName) {
if (!viewModel.loadedComponents[routeName]) {
ko.components.register(routeName, {
viewModel: {
require: 'components/' + routeName + '/' + routeName
},
template: {
require: 'text!components/' + routeName + '/' + routeName + '.html'
}
});
viewModel.loadedComponents[routeName] = true;
}
viewModel.currentComponent(routeName);
}
HTML:
<div data-bind="component: currentComponent"></div>
Таким образом:
Начиная с Knockout 3.2+, доступна поддержка асинхронных фабрик компонентов без сторонних загрузчиков.
ko.components.register('settings', function(params, componentInfo) {
fetch('/components/settings/template.html')
.then(r => r.text())
.then(html => {
componentInfo.element.innerHTML = html;
ko.applyBindings(new SettingsViewModel(params), componentInfo.element);
});
});
Такой подход подходит для окружений без AMD, но требует ручного управления жизненным циклом.
Часто удобнее хранить маршрут как объект конфигурации:
const routes = {
home: { component: 'home' },
profile: { component: 'profile' },
settings: { component: 'settings' }
};
В модели:
this.currentRoute = ko.observable('home');
this.currentComponent = ko.pureComputed(() => {
return routes[this.currentRoute()].component;
});
Регистрация компонентов по-прежнему выполняется лениво при первом использовании, но логика маршрутов становится централизованной и расширяемой.
При асинхронной загрузке возникает промежуток, когда компонент ещё не готов. Для этого вводится состояние загрузки:
this.isLoading = ko.observable(false);
При смене маршрута:
this.isLoading(true);
loadComponent(route).then(() => {
this.currentComponent(route);
this.isLoading(false);
});
HTML:
<div data-bind="visible: isLoading">Загрузка...</div>
<div data-bind="component: currentComponent, visible: !isLoading()"></div>
Это предотвращает отображение пустого контейнера и улучшает пользовательский опыт.
Knockout автоматически вызывает dispose у
computed и подписок, связанных с DOM-узлом компонента. Для
дополнительной очистки используется метод dispose в модели
компонента:
function ProfileViewModel() {
this.subscription = someObservable.subscribe(...);
this.dispose = function() {
this.subscription.dispose();
};
}
Ленивая загрузка в сочетании с корректным освобождением ресурсов предотвращает утечки памяти при частой навигации.
Типовая структура:
components/
├─ home/
│ ├─ home.js
│ └─ home.html
├─ profile/
│ ├─ profile.js
│ └─ profile.html
└─ settings/
├─ settings.js
└─ settings.html
Каждый компонент полностью изолирован и может загружаться независимо. Это упрощает сопровождение и масштабирование.
Ленивая загрузка компонентов по маршрутам превращает Knockout.js в полноценную основу для крупных SPA, сохраняя при этом простоту реактивной модели и прозрачность данных.