vue router는 vue.js의 공식 라우터이다.
<router-link>
는 <a>
태그로 렌더링된다.<router-link>
는 현재 라우트와 일치할 때 자동으로 .router-link-active
클래스가 추가된다.<router-view>
는 현재 라우트에 맞는 컴포넌트가 렌더링된다.<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 네비게이션을 위해 router-link 컴포넌트를 사용합니다. -->
<!-- 구체적인 속성은 `to` prop을 이용합니다. -->
<!-- 기본적으로 `<router-link>`는 `<a>` 태그로 렌더링됩니다.-->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 라우트 아울렛 -->
<!-- 현재 라우트에 맞는 컴포넌트가 렌더링됩니다. -->
<router-view></router-view>
</div>
라우트 정의하기
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
routes 옵션과 함께 router 인스턴스를 만들기
추가옵션은 여기에 정의해야 한다.
const router = new VueRouter({
routes // `routes: routes`의 줄임
})
루트 인스턴스에 router를 주입한다.
const app = new Vue({
router
}).$mount('#app')
컴포넌트에서 라우터에 접근한다.
this.$router
는 router
와 동일하다.this.$router
를 사용하는 이유는 라우터를 조작해야하는 모든 컴포넌트에서 라우트 객체를 가져올 필요가 없기 때문이다.// Home.vue
export default {
computed: {
username () {
// 곧 `params` 확인할 수 있습니다.
return this.$route.params.username
}
},
methods: {
goBack () {
window.history.length > 1
? this.$router.go(-1)
: this.$router.push('/')
}
}
}
모든 사용자에 대해 동일한 레이아웃을 가지지만, 다른 사용자 id로 렌더링되어야 하는 User 컴포넌트의 경우
동적 세그먼트는 콜론(:)으로 시작한다.
아래 예제의 경우 /user/foo
와 /user/bar
같은 URL은 같은 경로에 매핑된다.
라우트가 일치하면, 동적세그먼트의 값은 모든 컴포넌트에서 this.$route.params
로 표시된다.
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
// 동적 세그먼트는 콜론으로 시작합니다.
{ path: '/user/:id', component: User }
]
})
$route
객체를 watch orbeforeRouteUpdate
를 사용매개변수와 함께 라우터를 사용할 때는 사용자가 /user/foo에서 /user/bar로 이동할 때 동일한 컴포넌트 인스턴스가 재사용된다는 것이다. 이 경우에 컴포넌트의 라이프 사이클 훅이 호출되지 않는다.
동일한 컴포넌트의 params 변경사항에 반응하려면 $route
객체를 watch하면 된다. watch 안에서 fetch와 같이, param이 변경되었을 때 해야 하는 작업을 처리한다.
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 경로 변경에 반응하여...
}
}
}
또 다른 방법은 beforeRouteUpdate
를 사용하는 것이다. 여기서의 작업이 끝나고 다음 훅을 호출하거나, 라우트를 실행시키기 위해 마지막에 next()를 호출해야 한다.
const User = {
template: '...',
beforeRouteUpdate(to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
어떠한 url도 일치하지 않는 것을 404페이지로 넘기려면, *
정규식을 사용하면 된다. 이걸 제일 마지막에 두는 것을 잊지말자. => fallback!
asterisk(*)를 사용하면, $route.params.pathMath
에 *에 해당하는 데이터가 담기게 된다.
{
// will match everything
path: '*'
}
{
// will match anything starting with `/user-`
path: '/user-*'
}
// Given a route { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// Given a route { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'
vue 인스턴스 내부에서 라우터 인스턴스에 $router
로 접근할 수 있다.
예를 들어, this.$router.push
처럼 사용하면 된다.
다른 URL로 이동(히스토리에 넣기) : router.push(location, onComplete?, onAbort?)
새로운 항목을 히스토리 스택에 넣는다. 따라서 사용자가 브라우저의 뒤로가기 버튼을 클릭하면 이전 URL로 이동하게된다.
<router-link :to="...">
를 클릭하면, router.push를 호출하는 것과 같다.router.push
또는 router.replace
에 두 번째, 세 번째 전달인자로 onComplete
와 onAbort
콜백을 제공한다. 각각 탐색이 성공적으로 완료되거나 중단될 때 호출된다.beforeRouteUpdate
를 사용해야 params 변경사항에 반응한다.(e.g. 유저 정보를 fetching해오기)// 리터럴 string
router.push('home')
// object
router.push({ path: 'home' })
// 이름을 가지는 라우트
router.push({ name: 'user', params: { userId: 123 }})
// 쿼리와 함께 사용, 결과는 /register?plan=private 입니다.
router.push({ path: 'register', query: { plan: 'private' }})
다른 URL로 이동(히스토리에 넣지 않기) : router.replace(location)
히스토리에 항목을 추가하지 않고, 현재를 해당 location으로 대체한다.
<router-link :to="..." replace>
n번째 이전 or 이후로 이동하기 : router.go(n)
// go forward by one record, the same as history.forward()
router.go(1)
// go back by one record, the same as history.back()
router.go(-1)
// go forward by 3 records
router.go(3)
// fails silently if there aren't that many records.
router.go(-100)
router.go(100)
History Manipulation
router.push, router.replace, router.go는 window.history.pushState, window.history.replaceState, window.history.go에 대응한다.
라우트를 이름으로 명시하여 더 편리하게 만든다.
라우터 인스턴스를 생성할 때, routes
옵션에 라우트의 이름을 추가하면 된다.
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
이름이 있는 라우트를 연결하려면, router-link
컴포넌트의 to
prop에 객체를 넘기면 된다.
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
프로그래밍적으로는 router.push()
에 같은 객체를 넘기면 된다.
router.push({ name: 'user', params: { userId: 123 } })
중첩하는 것이 아닌, 여러 view를 보여줘야할 경우가 생긴다. 이때 named view를 사용하는 것이 좋다.
이름이 주어지지 않는 경우, default
가 이름이 된다.
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
컴포넌트에 $route
를 사용하는 것은 컴포넌트와 라우트 간의 강한 결합이 생기는 것이다. 이는 컴포넌트가 특정한 URL에서만 사용해야하므로, 컴포넌트의 유연함에 제약이 생기게 하는 것이다.
컴포넌트와 라우트의 결합을 푸는 방법은 props
옵션을 사용하는 것이다.
강한 결합의 예
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [{ path: '/user/:id', component: User }]
})
props
를 사용하여 decouple한 예
이 방식을 사용하면 컴포넌트를 어디에서든지 사용할 수 있게 된다. => 컴포넌트에 flexibility를 주게 된다.
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// for routes with named views, you have to define the `props` option for each named view:
{
path: '/user/:id',
components: {
default: User,
sidebar: Sidebar
},
props: {
default: true,
// function mode, more about it below
sidebar: route => ({ search: route.query.q })
}
}
]
})
props가 true가 되면, route.params가 컴포넌트의 props로 세팅된다.
props가 객체라면, 이 객체가 컴포넌트의 props로 넘어가게 된다.
props가 static한 경우에 유용하다.
const router = new VueRouter({
routes: [
{
path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false }
}
]
})
props를 리턴하는 함수를 사용해도 된다.
이 방식은 다른 타입으로 파라미터를 변경하고자 할 때 유용하다.
const router = new VueRouter({
routes: [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
})
/search?q=vue
URL은 SearchUser 컴포넌트의 props로 {query: 'vue'}
를 넘길 것이다.
라우팅을 설정한 후에, url을 직접 변경하였더니 서버에서 404를 응답하였다.
공식문서에는 history mode를 설정하였을 때, 앱이 client side app이기 때문에, 서버에서의 설정을 해주지 않으면 직접 url로 접근했을 때 404 에러를 받는다고 하였다.
history mode를 지우면, 디폴트 모드인 hash mode로 동작하게 된다.
hash mode는 url을 직접 입력하여도 라우팅에 맞는 컴포넌트가 렌더링되었다.
hash mode는 URL이 바뀌어도, 페이지를 다시 로딩하지 않게 하기 위해 URL hash('#') 형태로 서비스한다고 한다.
http://localhost:8080/#/boards
http://localhost:8080/#/login
http://localhost:8080/#
history mode는 이러한 hash(#)을 없앤 형태의 url이다.
http://localhost:8080/boards
http://localhost:8080/login
http://localhost:8080/
history mode가 일반적인 url의 형태이므로, history mode를 사용할 것을 선호한다.
그러나, URL에 직접 접근하면 404에러가 발생하므로, 따로 서버에 fallback route를 잡는 세팅을 해주어야 한다.
그렇다면 왜 히스토리 모드에서 404오류가 발생할까?
히스토리 모드에서는 URL에 작성된 것과 같은 이름으로 서버에 리소스를 요청한다.
서버에서 해당 URL 리소스가 있다면, 반환하고 없으면 404 에러를 반환할 것이다.
이와 반대로 해쉬모드에서는 index.html에 들어가서 vue-router에 해당되는 URL router가 있는지 확인한 뒤에 반환한다. 즉, 브라우저는 URL이 변경되었음을 인지한다. 그러나 vue에서는 서버에 리퀘스트를 보내지는 않는다. 요청을 하지 않았으므로 리로드는 없지만, 페이지가 각각의 고유한 URL을 가졌으므로 history 관리가 가능하다.