[Vue.js] Vue의 Router(라우터)

2019. 3. 21. 08:49Web/Vue.js

1. Vue Router란?


Vue에서 SPA(Single Page Application)을 구현 시, 사용자 요청 경로에 따라 해당하는 컴포넌트에 매핑하여 렌더링을 결정해주는 플러그인이 Vue Router이다.
원래 사용자의 URI Request가 들어오면 서버 즉 백엔드에서 Controller가 그 Request에 해당하는 Response로써 정적 파일(html, css, javascript ...)을 보내준다.
Vue Router는 프론트엔드에서 요청 URI에 따라 전체 새로운 돔을 변경하는 것이 아니라, 브라우져에서 변화가 있는 부분의 돔을 변경하는 방식이다.

2. 설치


CDN

html head 태그 안에 vue CDN 밑에 아래와 같이 추가해 줍니다. (vue를 CDN으로 이용하시는 경우 기준입니다.)

<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

NPM

npm을 이용하는 경우 아래와 같이 cmd 창에서 설치해주시고,

npm install vue-router

main.js 파일에서 아래와 같이 추가합시다.

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

3. Vue Router 설정방법(파일 분리)


3.1 간단한 방법

vue-router 공식문서에는 main.js에 routing path를 정의하고 사용합니다.

main.js

// 0. 모듈 시스템 (예: vue-cli)을 이용하고 있다면, Vue와 Vue 라우터를 import 하세요
// 그리고 `Vue.use(VueRouter)`를 호출하세요


// 1. 라우트 컴포넌트를 정의하세요.
// 아래 내용들은 다른 파일로부터 가져올 수 있습니다.
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. 라우트를 정의하세요.
// Each route should map to a component. The "component" can
// 각 라우트는 반드시 컴포넌트와 매핑되어야 합니다.
// "component"는 `Vue.extend()`를 통해 만들어진
// 실제 컴포넌트 생성자이거나 컴포넌트 옵션 객체입니다.
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. `routes` 옵션과 함께 router 인스턴스를 만드세요.
// 추가 옵션을 여기서 전달해야합니다.
// 지금은 간단하게 유지하겠습니다.
const router = new VueRouter({
  routes // `routes: routes`의 줄임
})

// 4. 루트 인스턴스를 만들고 mount 하세요.
// router와 router 옵션을 전체 앱에 주입합니다.
const app = new Vue({
  router
}).$mount('#app')

// 이제 앱이 시작됩니다!

3.2 추천하는 방법

하지만 다른 프로젝트에서 많이 사용하고, 저 또한 사용했던 방법은 routing path 파일(router.js)을 따로 분리하는 방법입니다.

router.js

import Dashboard from './spa/Dashboard/Dashboard.vue';
import LogViewer from './spa/LogViewer/LogViewer.vue';
import Keyword from './spa/Keyword/Keyword.vue';

export default[
  {path:'/', component:Dashboard},
  {path:'/logViewer', component:LogViewer},
  {path:'/keyword', component:Keyword}
]

main.js

import Routes from './router'

...

const router = new VueRouter({
  routes: Routes,
  mode : 'history' //history 모드
});

...

new Vue({
  render: h => h(App),
  router: router
}).$mount('#app')

App.vue

<template>
  <div id="container" class="effect aside-float aside-bright mainnav-sm page-fixedbar footer-fixed" style="background-color:#0b1521">
    <my-header></my-header>
    <div class="boxed">

      <router-view></router-view>

      <main-nav></main-nav>
    </div>
    <my-footer></my-footer>
  </div>
</template>

이렇게 분리하게 되면, 시각적으로 보기가 편하고 관리하기가 편해집니다.
나중에 children 속성까지 포함되면 매우 복잡해집니다...

4. 기본 사용법


여기서부터는 기본 사용법이므로 그냥 router.js 파일을 따로 분리하지 않고 진행하겠습니다.

4.1 동적 라우트 매칭

가끔 아니, 종종 일종의 URL 패턴에 따라서 매칭시켜야 하는 경우가 생깁니다.

예를 들어서 User의 정보를 조회하기 위한 URL패턴(/User/foo 와 /User/bar ...)의 경우 User 뒤의 foo와 bar는 일종의 사용자 ID라고 합시다.
그러면 Vue Router에서는 아래와 같이 쓸 수 있습니다.

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User }
  ]
})

이제 파라미터로 id를 받을 수 있게 되었고, template에서는 $route.params.id로 접근할 수 있게 되었습니다.

Params 변경 사항에 반응

공식 문서를 보다가, 중요한 개념을 발견해서 정리합니다.
위의 예제에서 /user/foo에서 /user/bar로 변경될 때, 동일한 컴포넌트 인스턴스가 재사용된다는 점입니다.
당연히 이전 인스턴스를 삭제하고 다시 생성하는 것보다 효율적이지만,
이는 또한 컴포넌트의 라이프 사이클 훅이 호출되지 않음을 의미합니다. (라이프 사이클은 다음 포스팅에서 다룰 예정입니다.)

그래서 여기서는 파라미터 변경 사항에 반응하기 위해 두 가지 방법을 소개하고 있습니다.

1) watch 속성 이용

const User = {
  template: '...',
  watch: {
    '$route' (to, from) {
      // 경로 변경에 반응하여...
    }
  }
}

2) Router의 beforeRouteUpdate 이용

const User = {
  template: '...',
  beforeRouteUpdate (to, from, next) {
    // react to route changes...
    // don't forget to call next()
  }
}

4.2 중첩된 라우트 (Children)

위에서 잠깐 언급했었는데, children이라는 속성입니다.
일반적으로 URL은 여러 단계로 중첩된 path로 이루어져 있습니다.
즉 예를 들어서, /user/profile, /user/posts ... 와 같이 한 곳에서 가지를 뻗어나간다고 해야 할까요..?
어쨌든 이런 경우를 다루기 위해서 children을 사용합니다.

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // /user/:id/profile 과 일치 할 때
          // UserProfile은 User의 <router-view> 내에 렌더링 됩니다.
          path: 'profile',
          component: UserProfile
        },
        {
          // /user/:id/posts 과 일치 할 때
          // UserPosts가 User의 <router-view> 내에 렌더링 됩니다.
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

그런데 여기서 어떠한 children path에 일치하지 않는 경우에는 그냥 하얀 페이지가 뜰텐데 이것을 다른 페이지를 띄우고 싶으시다면 아래와 같이 빈 path와 함께 컴포넌트를 children에 추가해 줍니다.

const router = new VueRouter({
  routes: [
    {
      path: '/user/:id', component: User,
      children: [
        // UserHome은 /user/:id 가 일치 할 때
        // User의 <router-view> 안에 렌더링됩니다.
        { path: '', component: UserHome },

        // ...또 다른 서브 라우트
      ]
    }
  ]
})

4.3 히스토리 모드

vue-router의 기본 모드는 해쉬 모드(hash mode)입니다. URL 해시를 사용하여 전체 URL을 시뮬레이트하므로 URL이 변경될 때 페이지가 다시 로드 되지 않습니다.
위에 예제 코드에 잠깐 나왔었는데, 간단하게 mode 속성에 history value만 추가해주면 됩니다.

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

이렇게 설정을 해주면, URL에 #(hash)가 제거 된 모습을 보실 수 있을 겁니다.
하지만, 여기서 끝이 아니라 히스토리 모드를 사용하게 되면 사용자가 직접 URL에 접근하는 경우 404 에러가 발생할 수 있습니다.
그래서 서버에 간단하게 포괄적인 대체 경로를 추가하기만 하면됩니다. URL이 정적 에셋과 일치하지 않으면 앱이 있는 동일한 index.html페이지를 제공해야 합니다.

서버 설정 예제

Apache

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /index.html [L]
</IfModule>

nginx

location / {
  try_files $uri $uri/ /index.html;
}

Native Node.js

const http = require("http")
const fs = require("fs")
const httpPort = 80

http.createServer((req, res) => {
  fs.readFile("index.htm", "utf-8", (err, content) => {
    if (err) {
      console.log('We cannot open "index.htm" file.')
    }

    res.writeHead(200, {
      "Content-Type": "text/html; charset=utf-8"
    })

    res.end(content)
  })
}).listen(httpPort, () => {
  console.log("Server listening on: http://localhost:%s", httpPort)
})

Express와 Node.js

Node.js/Express의 경우 connect-history-api-fallback 미들웨어 추천

Internet Information Services (IIS)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <system.webServer>
   <rewrite>
     <rules>
       <rule name="Handle History Mode and custom 404/500" stopProcessing="true">
         <match url="(.*)" />
         <conditions logicalGrouping="MatchAll">
           <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
           <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
         </conditions>
         <action type="Rewrite" url="index.html" />
       </rule>
     </rules>
   </rewrite>
     <httpErrors>     
         <remove statusCode="404" subStatusCode="-1" />                
         <remove statusCode="500" subStatusCode="-1" />
         <error statusCode="404" path="/survey/notfound" responseMode="ExecuteURL" />                
         <error statusCode="500" path="/survey/error" responseMode="ExecuteURL" />
     </httpErrors>
     <modules runAllManagedModulesForAllRequests="true"/>
 </system.webServer>
</configuration>

주의사항

이제 이렇게 설정하면 404에러를 보고하는 대신에 index.html 파일을 제공할 겁니다. 이 문제를 해결하려면 Vue 앱에서 catch-all 라우트를 구현하여 404 페이지를 표시해야합니다.

const router = new VueRouter({
  mode: 'history',
  routes: [
    { path: '*', component: NotFoundComponent }
  ]
})

또는 Node.js 서버를 사용하는 경우 서버 측의 라우터를 사용하여 들어오는 URL을 일치시키고 라우트가 일치하지 않으면 404로 응답하여 폴백을 구현할 수 있습니다. 더 자세한 설명은 Vue 서버사이드 렌더링 문서을 읽어보세요

'Web > Vue.js' 카테고리의 다른 글

[Vue] Vue.js 프로젝트 구조  (0) 2019.03.16
[Vue] Vue.js 개발 환경 셋팅  (0) 2019.03.04
[Vue] Vue.js란 무엇인가?  (0) 2019.03.04