Навигационные хуки
Как следует из названия, хуки навигации, предоставляемые Vue Router, в первую очередь используются для обеспечения безопасности навигации путем перенаправления или отмены перехода. Есть несколько способов внедрить навигационный хук: глобально, на уровне маршрута или внутри компонента.
Глобальные хуки перед переходом
Вы можете зарегистрировать глобальные хуки перед началом перехода навигации, используя router.beforeEach
:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// явное возвращение false для отмены перехода
return false
})
Глобальные навигационные хуки вызываются в порядке их создания при каждом навигационном переходе. Допускается асинхронное разрешение хуков — в этом случае переход считается незавершённым до тех пор, пока не будут разрешены все хуки.
В каждый навигационный хук передаётся три параметра:
to
: целевой маршрут в нормализованном формате, к которому осуществляется переход.from
: текущий маршрут в нормализованном формате с которого осуществляется переход.
Хук опционально может возвращать любое из следующих значений:
false
: отменить текущий переход. Если URL-адрес браузера был изменен (вручную пользователем или с помощью кнопки "Назад"), то он будет сброшен на URL-адрес маршрутаfrom
.Route Location: Перенаправляет на другое адрес, передавая маршрут, как если бы вы вызывали
router.push()
, что позволяет передавать такие опции, какreplace: true
илиname: 'home'
. Текущий переход навигации сбрасывается и создается новый с предыдущим значениемfrom
.jsrouter.beforeEach(async (to, from) => { if ( // проверка, что пользователь авторизован !isAuthenticated && // ❗️ Избежать бесконечного перенаправления to.name !== 'Login' ) { // перенаправить пользователя на страницу входа return { name: 'Login' } } })
Также хук может выбросить исключение с Error
, если возникла непредвиденная ситуация. Это приведет к отмене навигации и вызову любого коллбека, зарегистрированного через router.onError()
.
Если возвращается undefined
, true
или вообще ничего не возвращается, то навигация подтверждается, и вызывается следующий хук навигации.
Все, что описано выше, одинаково работает с асинхронными функциями и Promises:
router.beforeEach(async (to, from) => {
// canUserAccess() возвращает `true` или `false`
const canAccess = await canUserAccess(to)
if (!canAccess) return '/login'
})
Необязательный третий аргумент next
В предыдущих версиях Vue Router также была возможность использовать третий аргумент next
, это было распространенной причиной ошибок и прошло через RFC для его удаления. Тем не менее, он всё ещё поддерживается, что означает, что вы можете передать третий аргумент любому хуку навигации. В этом случае вы должны вызвать next ровно один раз при выполнении хука навигации. Он может появляться несколько раз, но только если логические пути не пересекаются, в противном случае хук не будет разрешен или вызовет ошибки. Вот плохой пример перенаправления пользователя по адресу /login
, если он не авторизован:
// ПЛОХО
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
// если пользователь не авторизован, то `next` вызывается дважды
next()
})
Вот правильная версия:
// ХОРОШО
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
Глобальные хуки разрешения перехода
Вы можете зарегистрировать данный глобальный хук разрешения перехода при помощи router.beforeResolve
. Он похож на router.beforeEach
, потому что срабатывает на каждый переход навигации, но разница в том, что хук resolve будет вызван непосредственно перед подтверждением перехода навигации, после того, как будут разрешены все хуки навигации в компоненте и асинхронные компоненты для маршрута. Вот пример, который обеспечивает, что пользователь предоставил доступ к камере для маршрутов, которые имеют определенное пользовательское meta свойство requiresCamera
:
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... обработать ошибку и отменить переход навигации
return false
} else {
// непредвиденная ошибка, отменить переход навигации и передать ошибку в глобальный обработчик
throw error
}
}
}
})
router.beforeResolve
- это идеальное место для получения данных или выполнения любой другой операции, которую вы хотите избежать, если пользователь не может зайти на страницу.
Глобальные хуки завершения перехода
Вы также можете зарегистрировать глобальные хуки завершения перехода, однако, в отличие от других хуков, эти хуки не получают функцию next
и не могут влиять на переход навигации:
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
Они полезны для аналитики, изменения заголовка страницы, обеспечения доступности, например, объявление страницы и многого другого.
Они также принимают сбои навигации в качестве третьего аргумента :
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
Подробнее о сбоях навигации можно узнать в этом руководстве.
Глобальные инъекции внутри хуков
С версии Vue 3.3 можно использовать inject()
внутри навигационных хуков. Это полезно для внедрения глобальных свойств, таких как хранилища pinia. Любой объект, предоставленный с помощью app.provide()
, также доступен в router.beforeEach()
, router.beforeResolve()
и router.afterEach()
:
// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')
// router.ts or main.ts
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// a pinia store
const userStore = useAuthStore()
// ...
})
Хуки для конкретных маршрутов
Хук beforeEnter
можно указать напрямую для конкретного маршрута в его конфигурации:
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// отменить переход навигации
return false
},
},
]
Хуки beforeEnter
срабатывают только при переходе на маршрут, они не срабатывают, когда меняется params
, query
или hash
, например, переходя с /users/2
на /users/3
или с /users/2#info
на /users/2#projects
. Они срабатывают только при переходе с другого маршрута.
В beforeEnter
можно передать массив функций, что удобно при повторном использовании хуков для разных маршрутов:
function removeQueryParams(to) {
if (Object.keys(to.query).length)
return { path: to.path, query: {}, hash: to.hash }
}
function removeHash(to) {
if (to.hash) return { path: to.path, query: to.query, hash: '' }
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]
При работе с вложенными маршрутами как родительские, так и дочерние маршруты могут использовать beforeEnter
. Если хук размещен в родительском маршруте, он не будет срабатывать при переходе между дочерними маршрутами с тем же родителем. Например:
const routes = [
{
path: '/user',
beforeEnter() {
// ...
},
children: [
{ path: 'list', component: UserList },
{ path: 'details', component: UserDetails },
],
},
]
В приведенном выше примере beforeEnter
не будет вызываться при переходе между /user/list
и /user/details
, так как они имеют один и тот же родительский маршрут. Если мы добавит хук beforeEnter
непосредственно на маршрут details
, то он будет вызываться при переходе между этими двумя маршрутами.
Совет
Похожее поведение хуков конкретного маршрута можно реализовать с помощью meta-свойства маршрута и глобальных навигационных хуков.
Хуки для конкретных компонентов
Наконец, внутри компонентов маршрута (тех, которые передаются в конфигурацию маршрутизатора) можно напрямую определить навигационные хуки.
Использование с Options API
В компоненты маршрута можно добавить следующие опции:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
<script>
export default {
beforeRouteEnter(to, from) {
// вызывается перед подтверждением маршрута, отображающего данный компонент.
// НЕ имеет доступа к экземпляру компонента через `this`,
// потому что он еще не был создан, когда вызывается этот хук!
},
beforeRouteUpdate(to, from) {
// вызывается, когда маршрут, по которому рендерится данный компонент, изменился,
// но этот компонент повторно используется в новом маршруте.
// Например, при наличии маршрута с динамическими параметрами `/users/:id`, при переходе между `/users/1` и `/users/2`,
// будет повторно использован один и тот же экземпляр компонента `UserDetails`, и при этом будет вызван данный хук.
// Поскольку компонент при этом уже смонтирован, хук навигации имеет доступ к экземпляру компонента `this`.
},
beforeRouteLeave(to, from) {
// вызывается при покидании маршрута, соответствующего текущему компоненту.
// Как и в случае с `beforeRouteUpdate`, он имеет доступ к экземпляру компонента `this`.
},
}
</script>
Хук beforeRouteEnter
НЕ имеет доступа к this
, потому что страж вызывается до подтверждения навигации, и, следовательно, новый компонент, который рендерит этот маршрут, еще не был создан.
Однако вы можете получить доступ к экземпляру, передав коллбек в next
. Коллбек будет вызван после подтверждения навигации, и экземпляр компонента будет передан коллбеку в качестве аргумента:
beforeRouteEnter (to, from, next) {
next(vm => {
// доступ к публичному экземпляру компонента через `vm`
})
}
Обратите внимание, что beforeRouteEnter
- это единственный хук, который поддерживает передачу коллбека в next
. Для beforeRouteUpdate
и beforeRouteLeave
, this
уже доступен, поэтому передача коллбека не требуется и, следовательно, не поддерживается:
beforeRouteUpdate (to, from) {
// просто используйте `this`
this.name = to.params.name
}
Хук покидания маршрута обычно используется, чтобы предотвратить случайное покидание маршрута с несохраненными изменениями. Переход навигации можно отменить, вернув false
.
beforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}
Использование с Composition API
Если вы пишете свой компонент, используя Composition API, вы можете добавить хуки изменения и покидания маршрута через onBeforeRouteUpdate
и onBeforeRouteLeave
соответственно. Дополнительные сведения можно найти в разделе Composition API.
Полная цепочка обработки навигации
- Срабатывание навигации.
- Вызов хуков
beforeRouteLeave
в деактивируемых компонентах. - Вызов глобальных хуков
beforeEach
. - Вызов хуков
beforeRouteUpdate
в переиспользуемых компонентах. - Вызов
beforeEnter
в конфигурации маршрута. - Разрешение асинхронных компонентов для маршрута.
- Вызов
beforeRouteEnter
в активируемых компонентах. - Вызов глобальных хуков
beforeResolve
. - Навигация подтверждена.
- Вызов глобальных хуков
afterEach
. - Выполняется обновление DOM.
- Вызов коллбеков, переданных в
next
в качестве третьего аргумента хуковbeforeRouteEnter
с созданными экземплярами компонентов.