본문 바로가기
VueJS/VueJS

[VueJS] 뷰 라우터(Vue Router) 라이프사이클(LifeCycle) (ft. Navigation Guards)

by 썸머워즈 2022. 11. 3.
반응형

Vue Router LifeCycle

뷰 라우터 라이프사이클 이라고는 했지만 결국에는 뷰 라우터의 내비게이션 가드(Navigation Guards)의 흐름이라고 생각하면 된다.

 

공식문서에서 안내하고 있는 전체적인 흐름은 다음과 같다.

  1. Navigation triggered.
  2. Call beforeRouteLeave guards in deactivated components.
  3. Call global beforeEach guards.
  4. Call beforeRouteUpdate guards in reused components.
  5. Call beforeEnter in route configs.
  6. Resolve async route components.
  7. Call beforeRouteEnter in activated components.
  8. Call global beforeResolve guards.
  9. Navigation is confirmed.
  10. Call global afterEach hooks.
  11. DOM updates triggered.
  12. Call callbacks passed to next in beforeRouteEnter guards with instantiated instances.

 

이 모든 내비게이션 가드 함수들은 두 개의 인수를 받는다.

  • to : 대상 경로 위치
  • from : 현재 경로 위치

그리고 선택적으로 다음 값 중 하나를 반환할 수 있다.

  • false : 현재 탐색을 취소한다. (그냥 이동 취소하고 생각하면 된다.)
  • 경로 : router.push()를 호출하는 것처럼 경로를 전달하여 다른 위치로 바로 이동한다. replace: true 또는 name: 'home'과 같은 옵션을 전달할 수 있다.
router.beforeEach(async (to, from) => {
  if (
    // make sure the user is authenticated
    !isAuthenticated &&
    // ❗️ Avoid an infinite redirect
    to.name !== 'Login'
  ) {
    // redirect the user to the login page
    return { name: 'Login' }
  }
})

위에서 분명 두 개의 인수를 받는다고는 하였는데, 선택적 인수인 "next"를 사용할 수 있다.

next 인수공식문서에 따르면 Vue Router의 이전 버전에서는 그냥 사용할 수 있었는데 이게 일반적인 실수의 원인이라 생각되어 RFC를 통해 제거했다고 말하고 있다.

(RFC는 실제로 Vue에 적용되기 위해 거쳐가는 공간이라고 생각하면 된다. 자세한 내용은 깃허브를 직접 들어가보는게 좋을 것 같다.)

 

그러나 아직 많이들 사용하고 있기 때문에 여전히 지원되는 것 같으며, 개인적인 생각으로는 굳이 필요하나 싶기도 하기 때문에 추 후 사라질 수도 있을 것 같기도 하다.

 

만약 사용하게 된다면 꼭 if/else처럼 구분하여 next가 두 번 이상 호출되지 않도록 사용해야 한다.

router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

 

자 이제 기본적인 정보들은 익혔다고 생각하고 좀 더 자세히 공식문서를 기반으로 각각의 내비게이션 가드(Navigation Guards)의 사용법과 역할에 대해 알아보자.


beforeRouteLeave

beforeRouteLeave는 다른 곳으로 경로를 옮겨 이동하려고 할 때 호출된다.

즉, 이동하려고 할 때 가장 먼저 실행되는 내비게이션 가드라고 생각하면 될 것 같다.

const UserDetails = {
  template: `...`,
  beforeRouteLeave(to, from) {
    // called when the route that renders this component is about to be navigated away from.
    // As with `beforeRouteUpdate`, it has access to `this` component instance.
      const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
      if (!answer) return false
  },
}

beforeRouteLeave 가드는 일반적으로 사용자가 저장되지 않은 상태로 다른 곳으로 이동하는 것을 방지하는 데 사용된다.

예를 들어 게시글을 작성하다가 다른 곳으로 이동한다거나 할 때 사용하면 좋다. (next변수가 없어야 위 방식으로 사용 가능)

 

next라는 변수도 존재하기는 하는데, next를 선언하고 사용할 경우에는 next를 이용해서 이동을 막아줘야 한다.

const UserDetails = {
  template: `...`,
  beforeRouteLeave(to, from, next) {
      const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
      answer ? next() : next(false)
  },
}

beforeEach

beforeEach는 뷰 라우터를 쓰다 보면 흔하게 듣게 되는 "전역 가드(Global Guards)"이다.

이 가드는 기존의 컴포넌트가 제거된 후 새로운 네비게이션이 시작될 때 호출된다.

const router = createRouter({ ... })

router.beforeEach((to, from) => {
  // ...
  // explicitly return false to cancel the navigation
  return false
})

beforeRouteUpdate

beforeRouteUpdate는 컴포넌트를 재사용 할 경우에만 발생하는 가드이다.

예를 들어 'users/:id'라는 경로가 주어질 경우 'users/1'과 'users/2' 사이를 탐색할 때 호출된다.

const UserDetails = {
  template: `...`,
  beforeRouteUpdate(to, from) {
    // called when the route that renders this component has changed, but this component is reused in the new route.
    // For example, given a route with params `/users/:id`, when we navigate between `/users/1` and `/users/2`,
    // the same `UserDetails` component instance will be reused, and this hook will be called when that happens.
    // Because the component is mounted while this happens, the navigation guard has access to `this` component instance.
  },
}

beforeEnter

beforeEnter는 이동하려는 라우트에 진입하기 전 호출되는 가드이다.

 

beforeEach와 비슷한 기능을 하는 건 맞지만 차이점은 beforeEach는 전역 가드라 전체적으로 지정하는 가드이며,

beforeEnter는 각 라우트마다 다르게 가드를 지정할 수 있다는 점이다.

(뭔가 공식문서에 따로 언급되지는 않았지만 지역 가드라고 지칭해도 되지 않으려나 싶다.)

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

단 하나 주의할 점은 "다른" 경로에서 탐색할 때만 호출된다는 점이다.

아마 이러한 설명은 아까 위에서 beforeRouteUpdate라는 가드가 실행될 때는 beforeEnter가 실행되지 않는다는 말이다.

(당연히 beforeEnter가 실행될 때는 다른곳에서 이동한 것이기 때문에 beforeRouteUpdate가 실행되지 않을 것이다.)

 

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],
  },
]

beforeRouteEnter

beforeRouteEnter는 새로운 컴포넌트를 만들기 전 호출되는 가드이다.

그렇기 때문에 this로 접근이 불가능하다.

const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // called before the route that renders this component is confirmed.
    // does NOT have access to `this` component instance,
    // because it has not been created yet when this guard is called!
  },
}

자 근데 beforeRouteEnter의 경우 위에 흐름을 보면 12번에 한번 더 호출되는 것을 알 수 있다.

정확하게는 beforeRouteEnter가 다시한번 호출되는 것이 아니라 next를 사용할 경우 next 콜백을 호출한다는 점이다.

 

특이하게도 이 콜백이 호출되는 가드는 beforeRouteEnter 가드가 유일하다고 한다.

(다른 곳에서는 this로 접근이 가능하기 때문이라고 한다.)

beforeRouteEnter (to, from, next) {
  next(vm => {
    // access to component public instance via `vm`
  })
}

만약 DOM이 업데이트 된 후 인스턴스에 접근하고 싶다면 위와 같이 next인수를 사용하여 콜백을 받아 사용하면 된다.


beforeResolve

beforeResolve는 구성 요소 내 가드 및 비동기 경로 구성 요소가 모두 해결된 후 호출된다.

즉, 내비게이션 작업을 완료하기 전 마지막으로 호출되는 가드라고 생각하면 된다.

 

beforeEach 전역 가드와 마찬가지로 어떤 페이지로 이동하는지에 상관없이 실행되는 전역 가드라고 볼 수 있다.

const router = createRouter({ ... })

router.beforeResolve((to, from) => {
  // ...
})

공식문서의 예제로는 사용자 정의 메타 속성에 의거하여 카메라에 대한 액세스 권한을 부여했는지 확인하는 예제라고 한다.

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... handle the error and then cancel the navigation
        return false
      } else {
        // unexpected error, cancel the navigation and pass the error to the global handler
        throw error
      }
    }
  }
})

afterEach

afterEach는 가드라기보다는 글로벌 훅이라고 불리며, 내비게이션 작업이 완료된 후 호출되는 훅이다.

이 훅은 next기능을 얻지 못하고 내비게이션 작업에 영향을 미칠 수 없다.

router.afterEach((to, from) => {
  // ...
})

분석, 페이지 제목 변경 등등의 접근성 기능 및 기타 여러 작업을 할 때 유용하게 사용된다고 한다.

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

또한 세 번째 인수로 failure를 가지고 있다.

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

failure에 대해서는 다른 문서를 봐야하는것 같다.


참고: https://router.vuejs.org/guide/advanced/navigation-guards.html

 

Navigation Guards | Vue Router

Navigation Guards As the name suggests, the navigation guards provided by Vue router are primarily used to guard navigations either by redirecting it or canceling it. There are a number of ways to hook into the route navigation process: globally, per-route

router.vuejs.org

반응형


댓글

TOP