Knockout.js не содержит встроенного механизма маршрутизации. Это осознанное архитектурное решение: фреймворк сосредоточен на декларативном связывании данных и реактивных представлениях. Маршрутизация выносится во внешний слой и интегрируется через сторонние библиотеки. Такой подход позволяет гибко выбирать роутер в зависимости от масштаба приложения, требований к URL, поддержке history API и совместимости с серверной частью.
Маршрутизатор в связке с Knockout.js отвечает за:
Независимо от выбранного роутера, интеграция строится вокруг нескольких ключевых идей:
if, with,
component или кастомные биндинги.Чаще всего используется контейнер:
<div data-bind="component: currentComponent"></div>
или
<div data-bind="with: currentViewModel">
<!-- шаблон -->
</div>
Sammy.js — один из самых популярных маршрутизаторов, исторически часто использовавшийся вместе с Knockout.js.
Особенности Sammy.js:
#/path);Пример структуры:
function AppViewModel() {
this.currentView = ko.observable();
}
var appVM = new AppViewModel();
var router = Sammy(function () {
this.get('#/home', function () {
appVM.currentView(new HomeViewModel());
});
this.get('#/users/:id', function () {
appVM.currentView(new UserViewModel(this.params.id));
});
});
ko.applyBindings(appVM);
router.run('#/home');
Ключевые моменты интеграции:
Недостаток Sammy.js — устаревшая архитектура и отсутствие поддержки
pushState.
Director — лёгкий роутер, ориентированный на простые SPA.
Характерные черты:
Пример:
var appVM = {
currentPage: ko.observable()
};
var routes = {
'/': function () {
appVM.currentPage('home');
},
'/about': function () {
appVM.currentPage('about');
}
};
var router = Router(routes);
router.init('/');
ko.applyBindings(appVM);
В шаблоне:
<div data-bind="if: currentPage() === 'home'">...</div>
<div data-bind="if: currentPage() === 'about'">...</div>
Плюсы подхода:
Минус — отсутствие встроенной поддержки сложных сценариев и вложенных маршрутов.
Backbone часто используется в паре с Knockout.js, где Backbone отвечает за маршрутизацию и модели, а Knockout — за биндинги.
Архитектурная схема:
Пример роутера:
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'profile/:id': 'profile'
},
home: function () {
appVM.setView(new HomeVM());
},
profile: function (id) {
appVM.setView(new ProfileVM(id));
}
});
Интеграция с Knockout:
function AppViewModel() {
this.currentVM = ko.observable();
this.setView = function (vm) {
this.currentVM(vm);
}.bind(this);
}
Особенности:
page.js — современный минималистичный клиентский роутер.
Преимущества:
Пример:
var appVM = {
view: ko.observable()
};
page('/', function () {
appVM.view(new HomeVM());
});
page('/settings', function () {
appVM.view(new SettingsVM());
});
page();
ko.applyBindings(appVM);
page.js хорошо подходит для компонентного подхода Knockout, особенно
в сочетании с ko.components.
Компоненты позволяют логически отделить маршрутизацию от реализации интерфейса.
Типовой паттерн:
this.currentComponent = ko.observable('home-page');
<div data-bind="component: currentComponent"></div>
Преимущества:
Маршрутизатору не требуется знать внутреннюю реализацию компонента.
При частой смене маршрутов важно корректно:
subscription.dispose()),Распространённая практика — реализация метода dispose во
view-моделях:
function ExampleVM() {
this.interval = setInterval(...);
this.dispose = function () {
clearInterval(this.interval);
};
}
Перед сменой маршрута:
if (appVM.currentVM() && appVM.currentVM().dispose) {
appVM.currentVM().dispose();
}
Роутер не должен быть единственным источником истины. URL отражает состояние, но не управляет бизнес-логикой напрямую. Knockout-обсерваблы остаются главным механизмом реактивности.
Рекомендуемый подход:
Это позволяет избежать расхождений между адресной строкой и реальным состоянием интерфейса.
Грамотная интеграция Knockout.js с маршрутизатором сохраняет декларативную природу фреймворка и обеспечивает масштабируемость SPA без усложнения архитектуры.