Knockout.js изначально разрабатывался как MVVM-фреймворк для декларативного связывания данных и интерфейса, без встроенного маршрутизатора. Поэтому управление историей браузера и URL в приложениях на Knockout.js всегда было отдельной задачей, решаемой с помощью стандартных браузерных API или сторонних библиотек. Ключевым инструментом здесь является History API, появившийся в HTML5.
History API предоставляет интерфейс для управления историей переходов без перезагрузки страницы. Основные элементы:
history.pushState(state, title, url)history.replaceState(state, title, url)popstatelocationМетоды pushState и replaceState позволяют
изменять URL и сохранять произвольное состояние, не инициируя навигацию
на сервер.
Пример:
history.pushState({ page: 'users' }, '', '/users');
URL изменится, но страница не перезагрузится. Состояние сохраняется в истории и может быть восстановлено при навигации назад или вперёд.
Knockout.js управляет состоянием интерфейса через observables. История браузера в этом контексте становится внешним источником состояния, который необходимо синхронизировать с view model.
Типовая схема:
Таким образом достигается двусторонняя синхронизация.
На практике состояние приложения обычно сериализуется в:
/users/42)?page=users&id=42)#/users/42 — устаревший, но всё ещё используемый
вариант)Для History API предпочтительнее использовать path и query string.
Пример ViewModel:
function AppViewModel() {
this.currentPage = ko.observable('home');
this.userId = ko.observable(null);
}
При изменении currentPage URL обновляется:
viewModel.currentPage.subscribe(function(page) {
const url = page === 'home' ? '/' : '/' + page;
history.pushState({ page }, '', url);
});
Навигация кнопками браузера инициирует событие popstate.
Оно срабатывает при переходе назад или вперёд.
window.addEventListener('popstate', function(event) {
if (event.state && event.state.page) {
viewModel.currentPage(event.state.page);
}
});
Важно учитывать, что:
popstate не срабатывает при pushStateevent.state может быть nullПри загрузке страницы необходимо разобрать текущий URL и привести ViewModel в соответствие.
function parseLocation() {
const path = location.pathname.replace('/', '');
return path || 'home';
}
viewModel.currentPage(parseLocation());
history.replaceState({ page: viewModel.currentPage() }, '', location.pathname);
replaceState используется, чтобы начальное состояние
попало в историю без добавления лишнего шага.
Для более сложных состояний применяется query string. Пример: фильтры, сортировка, пагинация.
function getQueryParams() {
return Object.fromEntries(new URLSearchParams(location.search));
}
Синхронизация с observable:
viewModel.page = ko.observable(1);
viewModel.page.subscribe(function(value) {
const params = new URLSearchParams(location.search);
params.set('page', value);
history.pushState({ page: value }, '', '?' + params.toString());
});
При popstate параметры читаются обратно и записываются в
observables.
В небольших приложениях возможно полностью отказаться от маршрутизатора:
if, visible,
component<div data-bind="if: currentPage() === 'users'">
<!-- users -->
</div>
Это упрощает архитектуру, но требует аккуратной ручной синхронизации.
Для крупных приложений History API обычно комбинируется с маршрутизаторами:
Knockout.js в таком случае отвечает только за ViewModel, а маршрутизатор:
Пример с page.js:
page('/users/:id', function(ctx) {
viewModel.currentPage('users');
viewModel.userId(ctx.params.id);
});
page();
Knockout Components хорошо сочетаются с URL-навигацией. Каждый компонент может соответствовать сегменту URL, а ViewModel верхнего уровня управляет переключением.
<!-- ko component: currentComponent --><!-- /ko -->
viewModel.currentComponent = ko.observable('home-page');
URL → currentComponent → загрузка нужного
компонента.
При работе с History API важно учитывать:
state в историиНадёжная схема всегда включает:
В старых браузерах или при простых требованиях используется
location.hash.
Плюсы:
Минусы:
Knockout.js одинаково работает с hash-подходом, так как логика полностью лежит в JavaScript.
Типичная архитектура в Knockout.js:
popstate восстанавливает состояниеТакой подход сохраняет чистоту MVVM-модели, не привязывая логику представления к механике навигации, и позволяет строить одностраничные приложения с предсказуемым поведением истории браузера.