Skip to content

Complete guide to

Mastering Pinia

written by its creator

Навигационные хуки

Как следует из названия, хуки навигации, предоставляемые Vue Router, в первую очередь используются для обеспечения безопасности навигации путем перенаправления или отмены перехода. Есть несколько способов внедрить навигационный хук: глобально, на уровне маршрута или внутри компонента.

Глобальные хуки перед переходом

Вы можете зарегистрировать глобальные хуки перед началом перехода навигации, используя router.beforeEach:

js
const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // явное возвращение false для отмены перехода
  return false
})

Глобальные навигационные хуки вызываются в порядке их создания при каждом навигационном переходе. Допускается асинхронное разрешение хуков — в этом случае переход считается незавершённым до тех пор, пока не будут разрешены все хуки.

В каждый навигационный хук передаётся три параметра:

Хук опционально может возвращать любое из следующих значений:

  • false: отменить текущий переход. Если URL-адрес браузера был изменен (вручную пользователем или с помощью кнопки "Назад"), то он будет сброшен на URL-адрес маршрута from.

  • Route Location: Перенаправляет на другое адрес, передавая маршрут, как если бы вы вызывали router.push(), что позволяет передавать такие опции, как replace: true или name: 'home'. Текущая переход навигации сбрасывается и создается новый с предыдущим значением from.

    js
    router.beforeEach(async (to, from) => {
      if (
        // проверка, что пользователь авторизован
        !isAuthenticated &&
        // ❗️ Избежать бесконечного перенаправления
        to.name !== 'Login'
      ) {
        // перенаправить пользователя на страницу входа
        return { name: 'Login' }
      }
    })

Также хук может выбросить исключение с Error, если возникла непредвиденная ситуация. Это приведет к отмене навигации и вызову любого коллбека, зарегистрированного через router.onError().

Если возвращается undefined, true или вообще ничего не возвращается, то навигация подтверждается, и вызывается следующий хук навигации.

Все, что описано выше, одинаково работает с асинхронными функциями и Promises:

js
router.beforeEach(async (to, from) => {
  // canUserAccess() возвращает `true` или `false`
  const canAccess = await canUserAccess(to)
  if (!canAccess) return '/login'
})

Необязательный третий аргумент next

В предыдущих версиях Vue Router также была возможность использовать третий аргумент next, это было распространенной причиной ошибок и прошло через RFC для его удаления. Тем не менее, он всё ещё поддерживается, что означает, что вы можете передать третий аргумент любому хуку навигации. В этом случае вы должны вызвать next ровно один раз при выполнении хука навигации. Он может появляться несколько раз, но только если логические пути не пересекаются, в противном случае хук не будет разрешен или вызовет ошибки. Вот плохой пример перенаправления пользователя по адресу /login, если он не авторизован:

js
// ПЛОХО
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // если пользователь не авторизован, то `next` вызывается дважды
  next()
})

Вот правильная версия:

js
// ХОРОШО
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

Глобальные хуки разрешения перехода

Вы можете зарегистрировать данный глобальный хук разрешения перехода при помощи router.beforeResolve. Он похож на router.beforeEach, потому что срабатывает на каждый переход навигации, но разница в том, что хук resolve будет вызван непосредственно перед подтверждением перехода навигации, после того, как будут разрешены все хуки навигации в компоненте и асинхронные компоненты для маршрута. Вот пример, который обеспечивает, что пользователь предоставил доступ к камере для маршрутов, которые имеют определенное пользовательское meta свойство requiresCamera:

js
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 и не могут влиять на переход навигации:

js
router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

Они полезны для аналитики, изменения заголовка страницы, обеспечения доступности, например, объявление страницы и многого другого.

Они также принимают сбои навигации в качестве третьего аргумента :

js
router.afterEach((to, from, failure) => {
  if (!failure) sendToAnalytics(to.fullPath)
})

Подробнее о сбоях навигации можно узнать в этом руководстве.

Глобальные инъекции внутри хуков

С версии Vue 3.3 можно использовать inject() внутри навигационных хуков. Это полезно для внедрения глобальных свойств, таких как хранилища pinia. Любой объект, предоставленный с помощью app.provide(), также доступен в router.beforeEach(), router.beforeResolve() и router.afterEach():

ts
// 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 можно указать напрямую для конкретного маршрута в его конфигурации:

js
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 можно передать массив функций, что удобно при повторном использовании хуков для разных маршрутов:

js
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. Если хук размещен в родительском маршруте, он не будет срабатывать при переходе между дочерними маршрутами с тем же родителем. Например:

js
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
js
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // вызывается перед подтверждением маршрута, отображающего данный компонент.
    // НЕ имеет доступа к экземпляру компонента через `this`,
    // потому что он еще не был создан, когда вызывается этот хук!
  },
  beforeRouteUpdate(to, from) {
    // вызывается, когда маршрут, по которому рендерится данный компонент, изменился,
    // но этот компонент повторно используется в новом маршруте.
    // Например, при наличии маршрута с динамическими параметрами `/users/:id`, при переходе между `/users/1` и `/users/2`,
    // будет повторно использован один и тот же экземпляр компонента `UserDetails`, и при этом будет вызван данный хук.
    // Поскольку компонент при этом уже смонтирован, хук навигации имеет доступ к экземпляру компонента `this`.
  },
  beforeRouteLeave(to, from) {
    // вызывается при покидании маршрута, соответствующего текущему компоненту.
    // Как и в случае с `beforeRouteUpdate`, он имеет доступ к экземпляру компонента `this`.
  },
}

Хук beforeRouteEnter НЕ имеет доступа к this, потому что страж вызывается до подтверждения навигации, и, следовательно, новый компонент, который рендерит этот маршрут, еще не был создан.

Однако вы можете получить доступ к экземпляру, передав коллбек в next. Коллбек будет вызван после подтверждения навигации, и экземпляр компонента будет передан коллбеку в качестве аргумента:

js
beforeRouteEnter (to, from, next) {
  next(vm => {
    // доступ к публичному экземпляру компонента через `vm`
  })
}

Обратите внимание, что beforeRouteEnter - это единственный хук, который поддерживает передачу коллбека в next. Для beforeRouteUpdate и beforeRouteLeave, this уже доступен, поэтому передача коллбека не требуется и, следовательно, не поддерживается:

js
beforeRouteUpdate (to, from) {
  // просто используйте `this`
  this.name = to.params.name
}

Хук покидания маршрута обычно используется, чтобы предотвратить случайное покидание маршрута с несохраненными изменениями. Переход навигации можно отменить, вернув false.

js
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 и функцию setup, вы можете добавить хуки изменения и покидания маршрута через onBeforeRouteUpdate и onBeforeRouteLeave соответственно. Дополнительные сведения можно найти в разделе Composition API.

Полная цепочка обработки навигации

  1. Срабатывание навигации.
  2. Вызов хуков beforeRouteLeave в деактивируемых компонентах.
  3. Вызов глобальных хуков beforeEach.
  4. Вызов хуков beforeRouteUpdate в переиспользуемых компонентах.
  5. Вызов beforeEnter в конфигурации маршрута.
  6. Разрешение асинхронных компонентов для маршрута.
  7. Вызов beforeRouteEnter в активируемых компонентах.
  8. Вызов глобальных хуков beforeResolve.
  9. Навигация подтверждена.
  10. Вызов глобальных хуков afterEach.
  11. Выполняется обновление DOM.
  12. Вызов коллбеков, переданных в next в качестве третьего аргумента хуков beforeRouteEnter с созданными экземплярами компонентов.

Released under the MIT License.