네비게이션 가드
주로 탐색을 리다이렉션 or 취소하여 탐색을 막는데 사용하게 됩니다.
가드를 연결하는 방법은 다양한데 다음 세 가지 종류가 있습니다.
- 전역
- 라우트별
- 컴포넌트 내부
BeforeGuard
BeforeGurad
는 탐색 이전에 실행되게 됩니다.
router Instance의 beforeEach()
메서드에 콜백 함수를 전달하여 등록합니다.
콜백함수에서 사용하는 parameter에는 다음과 같은 종류가 있습니다.
- to : 탐색 될 라우트 위치 객체
- from : 탐색 전 현재 라우트 위치 객체
만약 라우트 작업을 취소하고 싶을 경우엔 return false
로 취소 할 수 있습니다.
또한, return { name: ${route_name} }
을 통해 리다이렉션를 명시할 수 있습니다.
router.beforeEach(async (to, from) => {
if(
// 유저 로그인 여부 확인
!isAuthenticated &&
// 무한 리다이렉션 방지
to.name != 'login'
) {
// 로그인 페이지로 이동
return { name: 'login' }
}
})
예외 상황에 대해서도 Error
를 던질 수도 있습니다.
이 경우에 진행하던 탐색은 취소되며, router.onError()
메서드에 전달돼 등록된 모든 콜백함수를 호출하게 됩니다.
기본적으로 to
와 from
인자를 사용하지만, 선택적으로 세번째 인자 next
를 사용 할 수 있습니다.next
가 일반적인 실수의 원인이었으며, RFC를 이유로 제거되었습니다.
하지만, 여전히 지원은 되므로 참고해 봅시다.next
의 경우 트리거 되는 탐색 가드별 정확히 한 번만 next
를 호출해야 합니다.
그 이유는 무한루프의 늪에 빠질 수 있기 때문입니다.
다음 예제를 보면서 나쁜 예와 좋은 예를 보겠습니다.
// Bad Case
router.beforeEach((to, from, next) => {
if (to.name !== 'login' && !isAuthenticated) next({ name: 'login'})
// 사용자가 인증되지 않은 경우, `next`가 두번 호출됨
next()
})
// Good Case
router.beforeEach((to, from, next) => {
if (to.name !== 'login' && !isAuthenticated) next({ name: 'login'})
else next()
})
위와 같은 경우 두번 next()
를 호출하는 이유는 if
절에서 한번 그리고 로직의 흐름대로 마지막의 next()
이렇게 두번을 호출하게 됩니다. return
을 중간에 삽입해 해결 할 수 있겠지만, 보기 숭하겠죠???
beforeResolve Guard
router.beforeResolve
로 전역 가드를 등록할 수 있습니다.router.beforeEach
와 유사하지만, 컴포넌트 내 가드 및 비동기 라우트 컴포넌트가 모두 해결된 후, 최종적으로 탐색을 진행할 것인가를 결정하기 위한 목적으로 호출합니다.
라고... 공식문서에 작성되어 있지만, 무슨말인지 당최 못알아먹겠죠??(저는 그랬음...)
router.beforeResolve
의 경우에는 탐색되기 가장 직전에 동작하는 로직을 정의해 준다고 생각하면 되겠습니다.
예제를 살펴보겠습니다.
meta
에 정의한 속성인 requiresCamera
가 있는 라우트에 대해, 사용자가 카메라에 접근 권한을 부여 했는지에 대해 확인하는 예제입니다.
router.beforeResolve(async to => {
if(to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if(error instanceof NotAllowedError) {
// ... 오류를 처리한 다음 탐색을 취소
return false
} else {
// 예기치 않은 오류, 탐색을 취소하고 오류를 전역 핸들러에 전달
throw error
}
}
}
})
afterEach Hook
전역으로 탐색 후 훅을 등록할 수도 있지만, Guard와 달리 next
인자를 전달 받지 않으며 탐색에는 영향을 끼치지 않습니다.
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
애널리스틱, 페이지 <title>
변경, 페이지 정보를 알리는 접근성 기능 및 기타 여러 작업에 유용합니다.
세 번째 인자로 failure
을 받게 되는데, 탐색 실패인자입니다.
차후 자세하게 다룰 것이므로 일단은 있다는 것만 파악하고 넘어가도록 합시다.
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
Guard에서 전역 Injection 사용하기
Vue 3.3부터는 Navigation Guard
내에서 inject()
를 사용할 수 있습니다.Pinia Store
와 같은 전역 속성을 Injection하는 데 유용합니다.
// main.js
const app = createApp(App)
app.provide('global', '안녕 인젝션!')
// /src/router/index.js or main.js
router.beforeEach((to, from) => {
const global = inject('global')
// Pinia Store
const userStore = useAuthStore()
})
beforeEnter
라우트를 구성하는 객체에 직접 beforeEnter 가드를 정의할 수 있습니다.
이는 beforeEach
가 실행된 후 beforeRouteUpdate
이후 동작하게 됩니다.
유의사항으로는 라우트에 진입할 때만 가드를 실행하며, 내부적으로 params
, query
, hash
가 변경될 때 트리거하지 않습니다.
const routes = [
{
path: '/users/:id',
name: 'profile',
component: UserDetails,
beforeEnter: (to, from) => {
// 라우트 진입 거부
return false
},
},
]
함수를 배열로 만들어 전달할 수도 있습니다.
다른 라우트에 재사용을 할경우 용이하겠죠??
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],
},
]
Nested Route
의 경우에도 사용 할 수 있습니다.
하지만, 유의할 점이 있습니다.
부모, 자식 각각에 beforeEnter
를 작성할 수 있지만, 부모에 선언된 beforeEnter
의 경우 자식들간에 이동시 트리거되지 않습니다.
예를 들어보겠습니다.
아래와 같이 /user/list
에서 /user/details
으로의 화면 전환시에는 beforeEnter
를 선언했지만 트리거되지 않습니다. 대신 children
에 각각의 route
에 선언하면 라우트간 이동시 동작할 것입니다.
const routes = [
{
path: '/user',
beforeEnter: (to, from) => {
// ...
},
children: [
{ path: 'list', component:UserList },
{ path: 'details', component:UserDetails },
],
},
]
In-Component Guard
라우트를 구성하는 객체에 전달되는 라우트 컴포넌트
내의 <script>
or <script setup>
태그에서 라우트 탐색 가드를 직접 정의할 수 있습니다.
beforeRouteEnter(to, from, next)
컴포넌트를 렌더링하는 라우트가 결정 되기 전에 호출됩니다.beforeEnter
이전에 동작하는 Guard입니다.
이 시점에는 컴포넌트 인스턴스는 아직 생성 전이므로 this
를 통한 컴포넌트 인스턴스에 접근할 수 없습니다.
하지만, 3번째 인자인 next
에 콜백을 전달하여 인스턴스에 접근이 가능합니다.
// Composition API에서는 사용할 수 없음
beforeRouteEnter (to, from, next) {
next(vm => {
// `vm`을 통해 컴포넌트 공개 인스턴스에 접근
})
}
beforeRouteEnter
는 Hook의 콜백함수에 next인자가 전달되는 유일한 가드입니다.beforeRouteUpdate
및 beforeRouteLeave
의 경우 this
가 이미 사용가능하므로, 콜백을 전달할 필요가 없는 것이죠.
beforeRouteUpdate(to, from)
컴포넌트가 새로 렌더링되지 않고, 해당 컴포넌트를 재사용하고 데이터만 변경될 경우에만 동작합니다. /pages/1
에서 /pages/2
로 페이징 정보를 변경할 경우처럼 말이죠.
이 경우 this
의 사용이 가능해집니다.
// Options API
beforeRouteUpdate (to, from) {
// 컴포넌트 인스턴스에 접근하기 위해 `this`를 사용.
this.name = to.params.name
}
//Composition API
beforeRouteLeave(to, from)
beforeRouteLeave
의 경우, 페이지를 떠날 때 동작하게됩니다.
상황 예시를 들자면...
우리가 1시간을 공들여 작성한 게시글이 있습니다.
그러다가 실수로 페이지의 나가기 버튼이나 뒤로 가기버튼을 눌렀다고 가정해 봅시다.
우리가 공들여 작업한 데이터들이 한순간에 증발해 버리는 것이죠.
이럴경우 우리는 재차 확인을 통해 방지할 수 있는 것이죠.
// Options API
beforeRouteLeave (to, from) {
const answer = window.confirm('정말 떠나시겠습니까? 저장되지 않는 변경사항이 있습니다!')
if (!answer) return false
}
// Composition API
onBeforeRouteLeave (to, from) {
const answer = window.confirm('정말 떠나시겠습니까? 저장되지 않는 변경사항이 있습니다!')
if (!answer) return false
}
참고사항
전체적인 탐색 흐름은 아래와 같습니다.
- 탐색이 트리거됨.
- 비활성화된 컴포넌트에서
beforeRouteLeave
가드 호출. - 전역
beforeEach
가드 호출. - 재사용된 컴포넌트에서
beforeRouteUpdate
가드 호출. - 라우트를 구성하면서
beforeEnter
호출. - 비동기 라우트 컴포넌트를 해결(resolve).
- 활성화된 컴포넌트에서
beforeRouteEnter
호출. - 전역
beforeResolve
가드 호출. - 탐색이 승인됨.
- 전역
afterEach
훅 호출. - DOM 업데이트가 트리거됨.
- 인스턴스화 된 인스턴스 내부
beforeRouteEnter
가드에서 전달된next
콜백 호출.
'FrameWorks > Vue' 카테고리의 다른 글
VueRouter의 Data Fetching 파헤치기 (0) | 2024.06.27 |
---|---|
VueRouter의 Router Meta Field 파헤치기 (0) | 2024.06.27 |
VueRouter의 Diffrenet History Modes 파헤치기 (0) | 2024.06.26 |
VueRouter의 Active Links 파헤치기 (0) | 2024.06.23 |
VueRouter의 Route Component Props 파헤치기 (0) | 2024.06.23 |
댓글