Одностраничное приложение (SPA) работает без полной перезагрузки страницы: навигация между экранами реализуется на стороне клиента. Клиентская маршрутизация отвечает за сопоставление URL в адресной строке с текущим состоянием интерфейса и набором отображаемых компонентов. В контексте Knockout.js маршрутизация не входит в ядро фреймворка, но тесно интегрируется с его моделью данных и механизмом привязок.
Маршрутизация в SPA решает несколько задач:
Существует два основных подхода:
1. Использование сторонних роутеров
2. Самописная маршрутизация
location.hashpushState)Для учебных и средних по сложности проектов часто используется хеш-маршрутизация из-за простоты и широкой поддержки браузерами.
Хеш-маршрутизация использует часть URL после символа #.
Изменение хеша не вызывает перезагрузку страницы, но генерирует событие
hashchange.
Пример URL:
http://example.com/#/users/42
Браузер предоставляет доступ к хешу через
location.hash.
Типичная архитектура включает:
Общий принцип: маршрут определяет, какая ViewModel активна, а Knockout автоматически обновляет DOM.
Текущее представление удобно хранить в observable:
function AppViewModel() {
this.currentView = ko.observable();
this.currentParams = ko.observable({});
}
currentView определяет активный экран,
currentParams — параметры маршрута.
Простейший роутер можно реализовать как объект с таблицей маршрутов:
function Router(routes) {
this.routes = routes;
}
Router.prototype.resolve = function (hash) {
var route = hash.replace(/^#\/?/, '');
var parts = route.split('/');
var key = parts[0] || 'home';
if (this.routes[key]) {
return {
view: this.routes[key].view,
params: this.routes[key].params
? this.routes[key].params(parts)
: {}
};
}
return null;
};
Таблица маршрутов:
var routes = {
home: {
view: 'home-view'
},
users: {
view: 'users-view'
},
user: {
view: 'user-view',
params: function (parts) {
return { id: parts[1] };
}
}
};
var appVm = new AppViewModel();
var router = new Router(routes);
function onRouteChange() {
var result = router.resolve(location.hash);
if (result) {
appVm.currentView(result.view);
appVm.currentParams(result.params);
}
}
window.addEventListener('hashchange', onRouteChange);
onRouteChange();
Изменение URL автоматически приводит к смене состояния ViewModel.
Knockout поддерживает компоненты, что удобно для маршрутизации.
Регистрация компонентов:
ko.components.register('home-view', {
viewModel: function () {},
template: '<h1>Главная</h1>'
});
ko.components.register('user-view', {
viewModel: function (params) {
this.userId = params.id;
},
template: '<div>Пользователь: <span data-bind="text: userId"></span></div>'
});
Контейнер в HTML:
<div data-bind="component: {
name: currentView,
params: currentParams
}"></div>
Knockout автоматически пересоздаёт компонент при изменении
currentView.
Навигация осуществляется обычными ссылками с хешами:
<a href="#/home">Главная</a>
<a href="#/users">Пользователи</a>
<a href="#/user/42">Профиль</a>
Изменение хеша запускает механизм маршрутизации без дополнительных обработчиков.
Параметры маршрута передаются в компонент как обычные данные. При необходимости их можно сделать observable внутри ViewModel компонента:
function UserViewModel(params) {
this.userId = ko.observable(params.id);
}
Это позволяет реагировать на смену параметров без пересоздания компонента, если логика маршрутизатора это поддерживает.
Частый сценарий — загрузка данных при активации экрана:
function UsersViewModel() {
var self = this;
this.users = ko.observableArray([]);
fetch('/api/users')
.then(r => r.json())
.then(data => self.users(data));
}
Маршрут отвечает только за выбор экрана, а загрузка данных инкапсулирована в ViewModel.
Для повышения устойчивости приложения используется маршрут по умолчанию:
if (!result) {
location.hash = '#/home';
}
Либо отдельный экран ошибки:
routes.notfound = {
view: 'notfound-view'
};
Более современный подход — history.pushState,
позволяющий избавиться от #. Для Knockout принципиальных
отличий нет: изменяется только механизм отслеживания URL.
window.onpopst ate = function () {
resolveRoute(location.pathname);
};
history.pushState({}, '', '/users');
Недостаток — необходимость серверной настройки для возврата SPA по любому URL.
При использовании Sammy.js или Page.js маршрутизация становится декларативной:
page('/users/:id', function(ctx) {
appVm.currentView('user-view');
appVm.currentParams({ id: ctx.params.id });
});
Knockout в этом случае остаётся исключительно инструментом реактивного отображения.
В масштабных SPA маршруты группируются по модулям:
Каждый модуль содержит собственные ViewModel и шаблоны. Центральный роутер выполняет только диспетчеризацию.
Логика маршрутов легко тестируется отдельно от DOM:
Это достигается выносом маршрутизатора в чистый JavaScript без привязки к Knockout.
Грамотно реализованная клиентская маршрутизация превращает Knockout.js из простого MVVM-инструмента в полноценную платформу для построения SPA с управляемым состоянием и предсказуемым поведением интерфейса.