Маршрутизация в приложениях на Knockout.js не входит в состав самого фреймворка, однако на практике почти всегда используется совместно с внешними решениями или собственной реализацией. Ключевую роль при этом играют параметры маршрутов, позволяющие передавать состояние и контекст между экранами без перезагрузки страницы.
Knockout.js фокусируется на декларативном связывании данных и шаблонов. Маршруты реализуются поверх него — через:
location.hash;pushState,
popstate);Во всех вариантах маршрут представляет собой строку, содержащую шаблон пути и параметры, извлекаемые из этого пути.
Пример маршрута в hash-стиле:
#/users/42/profile
Здесь 42 — параметр маршрута, определяющий контекст
отображения.
Наиболее распространённый вариант. Параметры определяются положением в URL.
Пример шаблона:
#/users/:id
Фактический URL:
#/users/42
В результате сопоставления параметр id получает значение
"42".
Используются в query-строке:
#/users/profile?id=42&tab=settings
Параметры:
id = 42tab = settingsТакой формат удобен для необязательных или дополнительных параметров состояния.
На практике часто сочетаются оба подхода:
#/users/42/profile?tab=settings
Позиционные параметры определяют сущность, query-параметры — режим отображения.
Минимальная реализация маршрутизации может основываться на обработке
события hashchange.
window.addEventListener("hashchange", onRouteChanged);
function onRouteChanged() {
const hash = location.hash.slice(1); // users/42
const parts = hash.split("/");
const route = parts[0];
const id = parts[1];
}
Такой подход прост, но требует ручной обработки всех вариантов маршрутов и параметров.
Регулярные выражения позволяют формализовать маршруты.
const routePattern = /^users\/(\d+)$/;
function onRouteChanged() {
const hash = location.hash.slice(1);
const match = hash.match(routePattern);
if (match) {
const userId = match[1];
}
}
Этот способ повышает контроль над форматом параметров и упрощает валидацию.
Пример с Sammy.js:
Sammy(function() {
this.get('#/users/:id', function() {
const userId = this.params.id;
});
});
Библиотека берёт на себя:
Параметры маршрута почти всегда отображаются в observables, чтобы автоматически обновлять интерфейс.
function UserViewModel() {
this.userId = ko.observable(null);
}
const vm = new UserViewModel();
ko.applyBindings(vm);
При изменении маршрута:
vm.userId(userId);
Все элементы интерфейса, связанные с userId, обновляются
без дополнительного кода.
Изменение параметров маршрута может означать:
Пример с подпиской:
vm.userId.subscribe(function(newId) {
loadUser(newId);
});
Такой подход отделяет логику маршрутизации от бизнес-логики ViewModel.
Частый сценарий — загрузка данных по параметру маршрута.
function loadUser(id) {
fetch(`/api/users/${id}`)
.then(r => r.json())
.then(data => {
vm.user(data);
});
}
Важно учитывать:
Для этого используются дополнительные observables:
vm.isLoading = ko.observable(false);
Маршрут может быть вызван без обязательных параметров или с ошибочными значениями.
Примеры ситуаций:
#/users/#/users/abcОбработка выполняется на уровне маршрутизатора или ViewModel:
if (!id || isNaN(id)) {
navigateToNotFound();
}
Это предотвращает переход приложения в неконсистентное состояние.
Иногда изменение состояния внутри ViewModel должно отражаться в URL.
Пример:
vm.selectedTab.subscribe(function(tab) {
location.hash = `#/users/${vm.userId()}?tab=${tab}`;
});
Так достигается:
В крупных приложениях параметры маршрута распределяются между несколькими ViewModel.
Пример:
userId — глобальный контекст;tab — локальный контекст представления.Часто используется иерархия:
function RootViewModel() {
this.userId = ko.observable();
}
function ProfileViewModel(root) {
this.userId = root.userId;
}
Такой подход снижает связанность и упрощает повторное использование компонентов.
Параметры маршрута часто используются для выбора шаблона:
vm.currentTemplate = ko.computed(function() {
return vm.userId() ? "user-profile" : "user-list";
});
HTML:
<div data-bind="template: currentTemplate"></div>
Изменение маршрута автоматически приводит к смене представления.
При возврате к ранее открытому маршруту может потребоваться восстановление состояния.
Используются:
localStorage);const cache = {};
function loadUser(id) {
if (cache[id]) {
vm.user(cache[id]);
return;
}
}
Параметры маршрута в приложениях на Knockout.js выполняют роль связующего слоя между:
Грамотная обработка параметров маршрута позволяет сохранить декларативную природу Knockout.js, избегая жёсткой связки между URL, логикой и представлением, и обеспечивает масштабируемость даже при отсутствии встроенного роутера.