vue-router

vue router는 vue.js의 공식 라우터이다.

🍒 시작하기

HTML

  • <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>

JS

  1. 라우트 정의하기

    ​​​​const routes = [
    ​​​​  { path: '/foo', component: Foo },
    ​​​​  { path: '/bar', component: Bar }
    ​​​​]
    
    
  2. routes 옵션과 함께 router 인스턴스를 만들기

    추가옵션은 여기에 정의해야 한다.

    ​​​​const router = new VueRouter({
    ​​​​  routes // `routes: routes`의 줄임
    ​​​​})
    
    
  3. 루트 인스턴스에 router를 주입한다.

    ​​​​const app = new Vue({
    ​​​​  router
    ​​​​}).$mount('#app')
    
    
  4. 컴포넌트에서 라우터에 접근한다.

    • this.$routerrouter와 동일하다.
    • 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 }
  ]
})

params 변경 사항에 반응하기

  • TL;DR
    • 라우트는 같고, params만 변경되는 경우, 컴포넌트가 재사용되어 라이프사이클 훅이 호출되지 않음.
    • 이걸 의도하지 않았다면,
      • $route객체를 watch or
      • beforeRouteUpdate를 사용

매개변수와 함께 라우터를 사용할 때는 사용자가 /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()
  }
}

모든 404 라우트 잡기

어떠한 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를 호출하는 것과 같다.
    • 2.2.0버전 이후로, router.push 또는 router.replace에 두 번째, 세 번째 전달인자로 onCompleteonAbort 콜백을 제공한다. 각각 탐색이 성공적으로 완료되거나 중단될 때 호출된다.
    • 현재 라우트와 목적지는 같은데, params만 달라지는 경우(e.g. /users/1 -> /users/2), 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에 대응한다.

    History API - MDN

🍒 이름이 있는 Route

라우트를 이름으로 명시하여 더 편리하게 만든다.
라우터 인스턴스를 생성할 때, 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

중첩하는 것이 아닌, 여러 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
      }
    }
  ]
})

🍒 라우트 컴포넌트에 props 넘기기

컴포넌트에 $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 })
    ​​​​      }
    ​​​​    }
    ​​​​  ]
    ​​​​})
    

Boolean Mode

props가 true가 되면, route.params가 컴포넌트의 props로 세팅된다.

Object Mode

props가 객체라면, 이 객체가 컴포넌트의 props로 넘어가게 된다.
props가 static한 경우에 유용하다.

const router = new VueRouter({
  routes: [
    {
      path: '/promotion/from-newsletter',
      component: Promotion,
      props: { newsletterPopup: false }
    }
  ]
})

Function Mode

props를 리턴하는 함수를 사용해도 된다.
이 방식은 다른 타입으로 파라미터를 변경하고자 할 때 유용하다.

const router = new VueRouter({
  routes: [
    {
      path: '/search',
      component: SearchUser,
      props: route => ({ query: route.query.q })
    }
  ]
})

/search?q=vue URL은 SearchUser 컴포넌트의 props로 {query: 'vue'}를 넘길 것이다.

🍒 History mode vs Hash mode

라우팅을 설정한 후에, url을 직접 변경하였더니 서버에서 404를 응답하였다.

공식문서에는 history mode를 설정하였을 때, 앱이 client side app이기 때문에, 서버에서의 설정을 해주지 않으면 직접 url로 접근했을 때 404 에러를 받는다고 하였다.

history mode를 지우면, 디폴트 모드인 hash mode로 동작하게 된다.
hash mode는 url을 직접 입력하여도 라우팅에 맞는 컴포넌트가 렌더링되었다.

hash mode는 URL이 바뀌어도, 페이지를 다시 로딩하지 않게 하기 위해 URL hash('#') 형태로 서비스한다고 한다.

  • hash mode에서의 url 형태
    ​​​​http://localhost:8080/#/boards
    ​​​​http://localhost:8080/#/login
    ​​​​http://localhost:8080/#
    

history mode는 이러한 hash(#)을 없앤 형태의 url이다.

  • history mode에서의 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 관리가 가능하다.

참고

Select a repo