API
Config
Содержит всю логику работы со страницей
Настраиваемые свойства
ts | Путь маршрута, должен начинаться с / и может включать динамические сегменты |
ts | Статичные props, передаваемые в компонент страницы |
ts | Валидаторы для динамических сегментов path |
ts | Валидаторы для query параметров |
ts | Функция, возвращающая Promise с компонентом в параметре default |
ts | Функция жизненного цикла, вызываемая перед редиректом на страницу |
ts | Функция жизненного цикла, вызываемая перед уходом со страницы |
Системные свойства
ts | Соответствует ключу объекта |
ts | Поле default, возвращенное loader |
ts | Все экспорты, возвращенные |
config.params
В Reactive Route нет явного разделения статичных и динамических Config, просто сегменты пути с префиксом : контролируются валидаторами, разрешающими открывать страницу со значением из URL, и это отражается в типизации.
users: {
path: '/users', // no validators
loader: () => import('./pages/users')
},
userDetails: {
path: '/user/:id', // "id" is a param matched by the validator
params: {
id: (value) => /^\d+$/.test(value)
},
loader: () => import('./pages/userDetails')
}Тесты типизации Config
import { createConfigs } from '../../packages/core';
const loader = async () => ({ default: null });
const v = () => true;
createConfigs({
dynamic: { path: '/:id/:tab', params: { id: v, tab: v }, loader },
static: { path: '/', loader },
notFound: { path: '/404', props: {}, loader },
internalError: { path: '/500', props: {}, loader },
// @ts-expect-error "params" must be "never"
static1: { path: '/', loader, params: {} },
// @ts-expect-error "params" are required
dynamic1: { path: '/:id/:tab', loader },
// @ts-expect-error "params.tab" and "params.id" are required
dynamic2: { path: '/:id/:tab', params: {}, loader },
// @ts-expect-error "params.tab" is required
dynamic3: { path: '/:id/:tab', params: { id: v }, loader },
// @ts-expect-error "params" validators must be functions
dynamic4: { path: '/:id/:tab', params: { id: v, tab: '' }, loader },
// @ts-expect-error duplicates are not allowed
double1: { path: '/:id/:id', params: { id: v }, loader },
// @ts-expect-error duplicates are not allowed
double2: { path: '/:id/:id', loader },
// @ts-expect-error duplicates are not allowed
double3: { path: '/:id/:id/', params: { id: v }, loader },
// @ts-expect-error duplicates are not allowed
double4: { path: '/:id/:id/', loader },
// @ts-expect-error duplicates are not allowed
double5: { path: '/:id/:tab/:id', params: { id: v, tab: v }, loader },
// @ts-expect-error duplicates are not allowed
double6: { path: '/:id/:tab/:id', loader },
});
// @ts-expect-error internalError is required
createConfigs({ notFound: { path: '/404', loader } });
// @ts-expect-error notFound is required
createConfigs({ internalError: { path: '/500', loader } });
createConfigs({
// @ts-expect-error "query" is not allowed
notFound: { path: '/404', loader, query: {} },
// @ts-expect-error "params" are not allowed
internalError: { path: '/500', loader, params: { code: v } },
});
createConfigs({
// @ts-expect-error "beforeLeave" is not allowed
notFound: { path: '/404', loader, beforeLeave: () => undefined },
// @ts-expect-error "beforeEnter" is not allowed
internalError: { path: '/500', loader, beforeEnter: () => undefined },
});Важно
В валидаторы приходят декодированные значения для удобства работы с не-английскими URL:
id: (value) => console.log(value) покажет не with%20space а with space
Таким образом, Reactive Route гарантирует, что все параметры в router.state.userDetails.params прошли валидаторы и их можно безопасно использовать.
В рантайме path всегда источник истины, и даже при отключенном TS стабильный матчинг гарантирован. Порядок объявления Config не важен, в алгоритме роутера только одно правило — при полном совпадении URL и path эта конфигурация обладает наивысшим приоритетом, даже если она объявлена в конце. В остальных сценариях выигрывает первый Config, у которого прошли все валидаторы.
config.query
Описываются в том же формате, что и params, в виде валидаторов:
search: {
path: '/search',
query: {
userPrompt: (value) => value.length > 5
},
loader: () => import('./pages/search')
}Важно
В валидаторы попадают декодированные значения:
userPrompt: (value) => console.log(value) покажет не with%20space а with space
Все query параметры являются опциональными, и их отсутствие не приводит к редиректу на notFound.
const pageState = router.state.search;
await router.redirect({ name: 'search', query: { userPrompt: 'short' }})
console.log(pageState.query.userPrompt) // undefined
await router.redirect({ name: 'search', query: { userPrompt: 'enough' }})
console.log(pageState.query.userPrompt) // 'enough'При необходимости наличия определенных query можно их проверять в beforeEnter и редиректить на notFound если они не пришли в nextState.query.
config.beforeEnter
Эту асинхронную функцию можно использовать для перенаправления на другой Config, выполнения проверок аутентификации и загрузки данных. Необработанные ошибки приведут к рендеру internalError без изменения URL в браузере.
Аргументы
ts | Причина, по которой вызван beforeEnter. Если изменились и params и query, то new_params |
ts | Следующий предполагаемый State |
ts | Текущий активный State (undefined при самом первом редиректе) |
ts | Метод для редиректа внутри жизненного цикла. Так как createConfigs вызывается до создания роутера, здесь не получится использовать router.redirect |
dashboard: {
path: '/dashboard',
loader: () => import('./pages/dashboard'),
async beforeEnter({ redirect }) {
await api.loadUser();
if (!store.isAuthenticated()) {
return redirect({ name: 'login', query: { returnTo: 'dashboard' } });
}
await api.loadDashboard();
}
}Тесты типизации beforeEnter / beforeLeave
import { createConfigs } from '../../packages/core';
const loader = async () => ({ default: null });
createConfigs({
static: {
path: '/',
loader,
async beforeEnter({ currentState, nextState, reason, redirect }) {
currentState?.name;
currentState?.params;
currentState?.query;
// @ts-expect-error unknown "currentState" property is not available
currentState?.unknown;
nextState.name;
nextState.params;
nextState.query;
// @ts-expect-error unknown "nextState" property is not available
nextState.unknown;
reason satisfies 'unmodified' | 'new_query' | 'new_params' | 'new_config';
// @ts-expect-error unknown reason is not assignable
reason satisfies 'unknown';
redirect({ name: 'static' });
redirect({ name: 'static', replace: true });
redirect({ name: 'unknown' });
// @ts-expect-error "replace" must be boolean
redirect({ name: 'static', replace: 'true' });
// @ts-expect-error redirect params values must be strings
redirect({ name: 'dynamic', params: { id: 1 } });
},
async beforeLeave({ currentState, nextState, reason, preventRedirect }) {
currentState.name;
currentState.params;
currentState.query;
// @ts-expect-error unknown "currentState" property is not available
currentState.unknown;
nextState.name;
nextState.params;
nextState.query;
// @ts-expect-error unknown "nextState" property is not available
nextState.unknown;
reason satisfies 'unmodified' | 'new_query' | 'new_params' | 'new_config';
// @ts-expect-error unknown reason is not assignable
reason satisfies 'unknown';
preventRedirect();
// @ts-expect-error "preventRedirect" does not accept arguments
preventRedirect(true);
},
},
notFound: { path: '/404', loader },
internalError: { path: '/500', loader },
});Ограничения
Только в функциях жизненного цикла redirect, currentState и nextState имеют неполную типизацию (name - просто string) из-за ограничений TypeScript 5, поэтому при рефакторинге TS не выведет ошибок.
Совет
Всегда используйте return с redirect и preventRedirect для стабильной логики редиректов.
config.beforeLeave
Эту асинхронную функцию можно использовать для прерывания редиректа. Необработанные ошибки приведут к рендеру internalError без изменения URL в браузере.
Аргументы
ts | Причина, по которой вызван beforeLeave. Если изменились и params и query, то new_params |
ts | Следующий предполагаемый State |
ts | Текущий активный State |
ts | Метод для остановки редиректа |
dashboard: {
path: '/dashboard',
loader: () => import('./pages/dashboard'),
async beforeLeave({ preventRedirect, nextState }) {
if (nextState.name === 'login') return preventRedirect();
// Do not check for unsaved changes on the server
if (typeof window === 'undefined') return;
const hasUnsavedChanges = await api.checkForm();
if (hasUnsavedChanges && !window.confirm(
`You have unsaved changes. Are you sure you want to leave?`
)) return preventRedirect();
}
}Тесты типизации beforeEnter / beforeLeave
import { createConfigs } from '../../packages/core';
const loader = async () => ({ default: null });
createConfigs({
static: {
path: '/',
loader,
async beforeEnter({ currentState, nextState, reason, redirect }) {
currentState?.name;
currentState?.params;
currentState?.query;
// @ts-expect-error unknown "currentState" property is not available
currentState?.unknown;
nextState.name;
nextState.params;
nextState.query;
// @ts-expect-error unknown "nextState" property is not available
nextState.unknown;
reason satisfies 'unmodified' | 'new_query' | 'new_params' | 'new_config';
// @ts-expect-error unknown reason is not assignable
reason satisfies 'unknown';
redirect({ name: 'static' });
redirect({ name: 'static', replace: true });
redirect({ name: 'unknown' });
// @ts-expect-error "replace" must be boolean
redirect({ name: 'static', replace: 'true' });
// @ts-expect-error redirect params values must be strings
redirect({ name: 'dynamic', params: { id: 1 } });
},
async beforeLeave({ currentState, nextState, reason, preventRedirect }) {
currentState.name;
currentState.params;
currentState.query;
// @ts-expect-error unknown "currentState" property is not available
currentState.unknown;
nextState.name;
nextState.params;
nextState.query;
// @ts-expect-error unknown "nextState" property is not available
nextState.unknown;
reason satisfies 'unmodified' | 'new_query' | 'new_params' | 'new_config';
// @ts-expect-error unknown reason is not assignable
reason satisfies 'unknown';
preventRedirect();
// @ts-expect-error "preventRedirect" does not accept arguments
preventRedirect(true);
},
},
notFound: { path: '/404', loader },
internalError: { path: '/500', loader },
});Ограничения
Только в функциях жизненного цикла redirect, currentState и nextState имеют неполную типизацию (name - просто string) из-за ограничений TypeScript 5, поэтому при рефакторинге TS не выведет ошибок.
Совет
Всегда используйте return с redirect и preventRedirect для стабильной логики редиректов.
State
Реактивный объект, хранящийся в router.state.
Свойства
ts | Соответствует ключу Config |
ts | Проверенные и декодированные значения params. Все обязательно будут присутствовать |
ts | Проверенные и декодированные значения query. Все опциональны |
Тесты типизации State
import { createConfigs, createRouter } from '../../packages/core';
const loader = async () => ({ default: null });
const v: (value: string) => boolean = () => true;
const adapters = {} as any;
const configs = createConfigs({
static: { path: '/', loader },
staticQuery: { path: '/', query: { q: v }, loader },
dynamic: { path: '/:id', params: { id: v }, loader },
dynamicQuery: {
path: '/:id',
params: { id: v },
query: { q: v },
loader,
},
notFound: { path: '/404', loader },
internalError: { path: '/500', loader },
});
const router = createRouter({ configs, adapters });
// @ts-expect-error not existing name
router.state.unknown;
router.state.static!.name;
router.state.static!.params;
router.state.static!.query;
// @ts-expect-error "params" values are not available
router.state.static!.params.unknown;
// @ts-expect-error "query" values are not available
router.state.static!.query.unknown;
router.state.notFound!.name;
router.state.notFound!.params;
// @ts-expect-error "params" values are not available
router.state.notFound!.params.unknown;
router.state.notFound!.query;
// @ts-expect-error "query" values are not available
router.state.notFound!.query.unknown;
router.state.internalError!.name;
router.state.internalError!.params;
// @ts-expect-error "params" values are not available
router.state.internalError!.params.unknown;
router.state.internalError!.query;
// @ts-expect-error "query" values are not available
router.state.internalError!.query.unknown;
router.state.staticQuery!.name;
router.state.staticQuery!.params;
// @ts-expect-error "params" values are not available
router.state.staticQuery!.params.unknown;
router.state.staticQuery!.query.q;
// @ts-expect-error unknown "query" are not available
router.state.staticQuery!.query.unknown;
router.state.dynamic!.name;
router.state.dynamic!.params.id;
// @ts-expect-error unknown "params" are not available
router.state.dynamic!.params.unknown;
router.state.dynamic!.query;
// @ts-expect-error "query" values are not available
router.state.dynamic!.query.unknown;
router.state.dynamicQuery!.name;
router.state.dynamicQuery!.params.id;
router.state.dynamicQuery!.query.q;
// @ts-expect-error unknown "params" are not available
router.state.dynamicQuery!.params.unknown;
// @ts-expect-error unknown "query" are not available
router.state.dynamicQuery!.query.unknown;Type Narrowing для State
import { createConfigs, createRouter } from '../../packages/core';
const loader = async () => ({ default: null });
const v: (value: string) => boolean = () => true;
const adapters = {} as any;
const configs = createConfigs({
static: { path: '/', loader },
staticQuery: { path: '/', query: { q: v }, loader },
dynamic: { path: '/:id', params: { id: v }, loader },
dynamicQuery: {
path: '/:id',
params: { id: v },
query: { q: v },
loader,
},
notFound: { path: '/404', loader },
internalError: { path: '/500', loader },
});
const router = createRouter({ configs, adapters });
const state = router.urlToState('');
// @ts-expect-error not existing name
state.name === 'unknown';
if (state.name === 'notFound') {
state.name;
state.params;
// @ts-expect-error "params" values are not available
state.params.unknown;
state.query;
// @ts-expect-error "query" values are not available
state.query.unknown;
}
if (state.name === 'internalError') {
state.name;
state.params;
// @ts-expect-error "params" values are not available
state.params.unknown;
state.query;
// @ts-expect-error "query" values are not available
state.query.unknown;
}
if (state.name === 'static') {
state.name;
state.params;
state.query;
// @ts-expect-error "params" values are not available
state.params.unknown;
// @ts-expect-error "query" values are not available
state.query.unknown;
}
if (state.name === 'staticQuery') {
state.name;
state.params;
// @ts-expect-error "params" values are not available
state.params.unknown;
state.query.q;
// @ts-expect-error unknown "query" are not available
state.query.unknown;
}
if (state.name === 'dynamic') {
state.name;
state.params.id;
// @ts-expect-error unknown "params" are not available
state.params.unknown;
state.query;
// @ts-expect-error "query" values are not available
state.query.unknown;
}
if (state.name === 'dynamicQuery') {
state.name;
state.params.id;
state.query.q;
// @ts-expect-error unknown "params" are not available
state.params.unknown;
// @ts-expect-error unknown "query" are not available
state.query.unknown;
}Router
createRouter
Эта функция создает router.
Аргументы
ts | Объект с Configs |
ts | Адаптеры для системы реактивности |
ts | Глобальная функция жизненного цикла, которая выполняется только при изменении компонента (не страницы!) |
beforeComponentChange
Эта функция вызывается только при изменении отрендеренного компонента и предназначена для использования в модульных архитектурах.
// Some page exports "export class PageStore { data: {}, destroy() {} }"
const globalStore = { pages: {} };
createRouter({
configs,
adapters,
beforeComponentChange({ prevConfig, currentConfig }) {
// PageStore is accessible from the otherExports object
const { PageStore } = currentConfig.otherExports;
if (PageStore) globalStore.pages[currentConfig.name] = new PageStore();
// destroy the obsolete PageStore from the previuos page
globalStore.pages[prevConfig.name]?.destroy();
}
})Тесты типизации beforeComponentChange
import { createConfigs, createRouter } from '../../packages/core';
const loader = async () => ({ default: null });
const v: (value: string) => boolean = () => true;
const adapters = {} as any;
const configs = createConfigs({
static: { path: '/', loader },
staticQuery: { path: '/', query: { q: v }, loader },
dynamic: { path: '/:id', params: { id: v }, loader },
dynamicQuery: {
path: '/:id',
params: { id: v },
query: { q: v },
loader,
},
notFound: { path: '/404', loader },
internalError: { path: '/500', loader },
});
createRouter({
configs,
adapters,
beforeComponentChange({ prevConfig, prevState, currentConfig, currentState }) {
/** prevConfig */
prevConfig?.name;
prevConfig?.loader;
// @ts-expect-error unknown prevConfig property is not available
prevConfig?.unknown;
if (prevConfig?.name === 'dynamic') {
prevConfig.params.id;
// @ts-expect-error unknown "params" are not available
prevConfig.params.unknown;
// @ts-expect-error "query" is not available
prevConfig.query.q;
}
if (prevConfig?.name === 'static') {
// @ts-expect-error "params" is not available
prevConfig.params.id;
// @ts-expect-error "query" is not available
prevConfig.query.q;
}
if (prevConfig?.name === 'staticQuery') {
prevConfig.query.q;
// @ts-expect-error unknown "query" are not available
prevConfig.query.unknown;
// @ts-expect-error "params" is not available
prevConfig.params.id;
}
if (prevConfig?.name === 'dynamicQuery') {
prevConfig.params.id;
prevConfig.query.q;
// @ts-expect-error unknown "params" are not available
prevConfig.params.unknown;
// @ts-expect-error unknown "query" are not available
prevConfig.query.unknown;
}
if (prevConfig?.name === 'notFound') {
// @ts-expect-error "params" is not available
prevConfig.params.code;
// @ts-expect-error "query" is not available
prevConfig.query.q;
}
if (prevConfig?.name === 'internalError') {
// @ts-expect-error "params" is not available
prevConfig.params.code;
// @ts-expect-error "query" is not available
prevConfig.query.q;
}
/** currentConfig */
currentConfig.name;
currentConfig.loader;
// @ts-expect-error unknown currentConfig property is not available
currentConfig.unknown;
if (currentConfig.name === 'notFound') {
// @ts-expect-error "params" is not available
currentConfig.params.code;
// @ts-expect-error "query" is not available
currentConfig.query.q;
}
if (currentConfig.name === 'internalError') {
// @ts-expect-error "params" is not available
currentConfig.params.code;
// @ts-expect-error "query" is not available
currentConfig.query.q;
}
if (currentConfig.name === 'static') {
// @ts-expect-error "params" is not available
currentConfig.params.id;
// @ts-expect-error "query" is not available
currentConfig.query.q;
}
if (currentConfig.name === 'staticQuery') {
currentConfig.query.q;
// @ts-expect-error unknown "query" are not available
currentConfig.query.unknown;
// @ts-expect-error "params" is not available
currentConfig.params.id;
}
if (currentConfig.name === 'dynamicQuery') {
currentConfig.params.id;
currentConfig.query.q;
// @ts-expect-error unknown "params" are not available
currentConfig.params.unknown;
// @ts-expect-error unknown "query" are not available
currentConfig.query.unknown;
}
if (currentConfig.name === 'dynamic') {
currentConfig.params.id;
// @ts-expect-error unknown "params" are not available
currentConfig.params.unknown;
// @ts-expect-error "query" is not available
currentConfig.query.q;
}
/** prevState */
prevState?.name;
prevState?.params;
prevState?.query;
// @ts-expect-error unknown prevState property is not available
prevState?.unknown;
// @ts-expect-error not existing name
prevState?.name === 'unknown';
if (prevState?.name === 'staticQuery') {
prevState.query.q;
// @ts-expect-error unknown "query" are not available
prevState.query.unknown;
// @ts-expect-error "params" values are not available
prevState.params.unknown;
}
if (prevState?.name === 'notFound') {
prevState.params;
// @ts-expect-error "params" values are not available
prevState.params.unknown;
prevState.query;
// @ts-expect-error "query" values are not available
prevState.query.unknown;
}
if (prevState?.name === 'internalError') {
prevState.params;
// @ts-expect-error "params" values are not available
prevState.params.unknown;
prevState.query;
// @ts-expect-error "query" values are not available
prevState.query.unknown;
}
if (prevState?.name === 'static') {
prevState.params;
prevState.query;
// @ts-expect-error "params" values are not available
prevState.params.unknown;
// @ts-expect-error "query" values are not available
prevState.query.unknown;
}
if (prevState?.name === 'dynamic') {
prevState.params.id;
// @ts-expect-error unknown "params" are not available
prevState.params.unknown;
prevState.query;
// @ts-expect-error "query" values are not available
prevState.query.unknown;
}
if (prevState?.name === 'dynamicQuery') {
prevState.params.id;
prevState.query.q;
// @ts-expect-error unknown "params" are not available
prevState.params.unknown;
// @ts-expect-error unknown "query" are not available
prevState.query.unknown;
}
/** currentState */
currentState.name;
currentState.params;
currentState.query;
// @ts-expect-error unknown currentState property is not available
currentState.unknown;
// @ts-expect-error not existing name
currentState.name === 'unknown';
if (currentState.name === 'notFound') {
currentState.params;
// @ts-expect-error "params" values are not available
currentState.params.unknown;
currentState.query;
// @ts-expect-error "query" values are not available
currentState.query.unknown;
}
if (currentState.name === 'internalError') {
currentState.params;
// @ts-expect-error "params" values are not available
currentState.params.unknown;
currentState.query;
// @ts-expect-error "query" values are not available
currentState.query.unknown;
}
if (currentState.name === 'static') {
currentState.params;
currentState.query;
// @ts-expect-error "params" values are not available
currentState.params.unknown;
// @ts-expect-error "query" values are not available
currentState.query.unknown;
}
if (currentState.name === 'staticQuery') {
currentState.query.q;
// @ts-expect-error unknown "query" are not available
currentState.query.unknown;
// @ts-expect-error "params" values are not available
currentState.params.unknown;
}
if (currentState.name === 'dynamic') {
currentState.params.id;
// @ts-expect-error unknown "params" are not available
currentState.params.unknown;
// @ts-expect-error "query" values are not available
currentState.query.unknown;
}
if (currentState.name === 'dynamicQuery') {
currentState.params.id;
currentState.query.q;
// @ts-expect-error unknown "params" are not available
currentState.params.unknown;
// @ts-expect-error unknown "query" are not available
currentState.query.unknown;
}
},
});Таким образом страница user может получать доступ к своему PageStore через globalStore.pages.user. Это позволяет более эффективно использовать code-splitting и сериализовывать только globalStore при SSR - в нем уже будут данные для необходимой страницы.
Также эту функцию можно использовать для прерывания асинхронных операций и подписок.
router.redirect
Выполняет полный цикл редиректа, подробнее описано в принципах работы. Если передать дополнительное свойство replace: true, то последний элемент истории браузера будет заменен. Возвращает строку с новым URL.
Вторым аргументом принимает skipLifecycle?: boolean если нужно пропустить вызовы beforeEnter и beforeLeave.
const newUrl = await router.redirect({
name: 'user',
params: { id: '9999' },
query: { phone: '123456' }
})
// '/user/9999?phone=123456'Тесты типизации StateDynamic
import { createConfigs, createRouter } from '../../packages/core';
const loader = async () => ({ default: null });
const v: (value: string) => boolean = () => true;
const adapters = {} as any;
const configs = createConfigs({
static: { path: '/', loader },
staticQuery: { path: '/', query: { q: v }, loader },
dynamic: { path: '/:id', params: { id: v }, loader },
dynamicQuery: {
path: '/:id',
params: { id: v },
query: { q: v },
loader,
},
notFound: { path: '/404', loader },
internalError: { path: '/500', loader },
});
const router = createRouter({ configs, adapters });
await router.redirect({ name: 'static' });
await router.redirect({ name: 'staticQuery' });
await router.redirect({ name: 'staticQuery', query: { q: '' } });
await router.redirect({ name: 'dynamic', params: { id: '' } });
await router.redirect({ name: 'dynamicQuery', params: { id: '' } });
await router.redirect({
name: 'dynamicQuery',
params: { id: '' },
query: { q: '' },
});
/** Not string values raise errors */
await router.redirect({
// @ts-expect-error name must be a string
name: 1,
});
await router.redirect({
name: 'staticQuery',
// @ts-expect-error "query" values must be strings
query: { q: 1 },
});
await router.redirect({
name: 'dynamic',
// @ts-expect-error "params" values must be strings
params: { id: 1 },
});
await router.redirect({
name: 'dynamicQuery',
params: { id: '' },
// @ts-expect-error "query" values must be strings
query: { q: 1 },
});
// @ts-expect-error "params" are required
await router.redirect({ name: 'dynamic' });
await router.redirect({
name: 'dynamic',
// @ts-expect-error "params" are incomplete
params: {},
});
/** Irrelevant StateDynamic raise errors */
// @ts-expect-error name must be present
await router.redirect({});
// @ts-expect-error "name" must be a string key of configs
await router.redirect({ name: 'unknown' });
await router.redirect({
name: 'static',
// @ts-expect-error "params" are not allowed
params: { id: '' },
});
await router.redirect({
name: 'staticQuery',
// @ts-expect-error "params" are not allowed
params: { id: '' },
});
await router.redirect({
name: 'dynamic',
// @ts-expect-error extra "params" are not allowed
params: { id: '', extra: '' },
});
await router.redirect({
name: 'static',
// @ts-expect-error "query" is not allowed
query: { q: '' },
});
await router.redirect({
name: 'dynamic',
params: { id: '' },
// @ts-expect-error "query" is not allowed
query: { q: '' },
});
await router.redirect({
name: 'staticQuery',
// @ts-expect-error unknown "query" key
query: { unknown: '' },
});
await router.redirect({
name: 'dynamicQuery',
params: { id: '' },
// @ts-expect-error unknown "query" key
query: { unknown: '' },
});
const state = router.urlToState('');
void router.redirect(state);router.urlToState
Принимает URL и возвращает State с фолбэком на notFound.
router.urlToState(`/user/9999?phone=123456>m=value`);
// {
// name: 'user',
// params: { id: '9999' },
// query: { phone: '123456' }
// }
router.urlToState(`/not-existing/admin?hacker=sql-inject`);
// {
// name: 'notFound',
// params: {},
// query: {}
// }Дополнительно
Сохраняются только описанные query, прошедшие валидацию, в данном случае gtm не попал в State.
router.init
Сокращенная форма router.redirect(router.urlToState(url)). Вторым аргументом принимает skipLifecycle?: boolean если нужно пропустить вызовы beforeEnter и beforeLeave.
// browser usage example:
await router.init(location.href)
// browser usage with SSR example:
await router.init(location.href, { skipLifecycle: true })
// Express.js server usage example:
const newUrl = await router.init(req.originalUrl)
// Optional step if you want to clear irrelevant query
if (req.originalUrl !== newUrl) res.redirect(newUrl)router.state
Реактивный объект, ключами которого являются name, а значениями — State, например:
console.log(router.state.user);
// reactive object
// {
// name: 'user',
// params: { id: '9999' },
// query: { phone: '123456' }
// }Предназначен для отображения значений в UI и для описания логики в autoruns/effects. При редиректе с новыми params или query эти значения соответственно изменятся в router.state.user.
Роутер не уничтожает старый State при переходе на другой Config. В данном примере если перейти на router.redirect({ name: 'home' }), все равно будет присутствовать router.state.user. Это помогает решить проблему с неочищенными подписками на старое состояние в рантайме.
Если бы Reactive Route использовал хранение только одного активного router.getActiveState() (это несуществующий метод!), как многие нереактивные роутеры, то подписка запустилась бы до unmount компонента с некорректным State, в котором может не быть этих параметров.
// with mobx adapter
import { autorun } from 'mobx'
function PageUser() {
const { router } = useContext(RouterContext);
const pageState = router.state.user!;
useEffect(() => autorun(() => {
console.log(pageState.params.id, pageState.query.phone);
}), []);
}// with kr-observable adapter
import { autorun } from 'kr-observable'
function PageUser() {
const { router } = useContext(RouterContext);
const pageState = router.state.user!;
useEffect(() => autorun(() => {
console.log(pageState.params.id, pageState.query.phone);
}), []);
}// with solid adapter
import { createRenderEffect } from 'solid-js'
function PageUser() {
const { router } = useContext(RouterContext);
const pageState = router.state.user!;
createRenderEffect(() => {
console.log(pageState.params.id, pageState.query.phone);
})
}<script lang="ts" setup>
// with vue adapter
import { watchEffect } from 'vue';
const { router } = useRouterStore();
const pageState = router.state.user!;
watchEffect(() => {
console.log(pageState.params.id, pageState.query.phone);
});
</script>router.isRedirecting
Реактивный boolean для отображения индикаторов загрузки при редиректах. Ниже показаны примеры глобального и локального отображения:
function GlobalLoader() {
const { router } = useContext(RouterContext);
return router.isRedirecting ? <Loader/> : null;
}
function SomeComponent() {
const { router } = useContext(RouterContext);
return <Button isLoading={router.isRedirecting}>Send</Button>;
}function GlobalLoader() {
const { router } = useContext(RouterContext);
return router.isRedirecting ? <Loader/> : null;
}
function SomeComponent() {
const { router } = useContext(RouterContext);
return <Button isLoading={router.isRedirecting}>Send</Button>;
}function GlobalLoader() {
const { router } = useContext(RouterContext);
return <Show when={router.isRedirecting}><Loader/></Show>;
}
function SomeComponent() {
const { router } = useContext(RouterContext);
return <Button isLoading={router.isRedirecting}>Send</Button>;
}<script lang="ts" setup>
import { useRouter } from '../../router';
const { router } = useRouter();
</script>
<template>
<Loader v-if="router.isRedirecting"/>
<Button :is-loading="router.isRedirecting">Send</Button>
</template>router.activeName
Реактивный name активного State (undefined до самого первого редиректа).
router.preloadComponent
Reactive Route загружает чанки страниц (выполняет loader) только во время редиректов. Эта функция может использоваться для предварительной загрузки и принимает name
await router.init(location.href);
setTimeout(async () => {
try {
await router.preloadComponent('login')
await router.preloadComponent('dashboard')
} catch (e) {
console.error('Seems like the user lost connection')
}
}, 5000)router.getGlobalArguments
Позволяет получить конфигурацию, переданную в createRouter.