|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "vue와 vue-router, vuex 함께 사용하기" |
| 4 | +date: 2017-01-08 08:45:31 +0530 |
| 5 | +categories: update |
| 6 | +author: "ChangJoo Park" |
| 7 | +excerpt: "vue의 컴패니언 라이브러리를 사용하는 방법을 빠르게 살펴봅니다." |
| 8 | +--- |
| 9 | + |
| 10 | +# Vue의 컴패니언 라이브러리 (vue-router, vuex) |
| 11 | + |
| 12 | +이 글은 vuex 와 vue-router를 **적용**하는 방법에 대해서 알아봅니다. 매우 간략한 내용만 다루고 있으므로 더 자세한 사용 방법에 대한 내용은 [vuex 한국어 문서](https://vuex.vuejs.org/kr/)와 [vue-router 한국어 문서](https://router.vuejs.org/kr/)를 읽어보세요. |
| 13 | + |
| 14 | +## vue-cli |
| 15 | + |
| 16 | +vue-cli는 기본적으로 vue만 포함하고 있습니다. [vuejs 템플릿 github 조직](https://github.com/vuejs-templates)은 vue-cli의 템플릿을 간결하게 유지하고 싶기 때문에 vuex 또는 vue-router를 포함하려 하지 않고 있습니다. 이와 관련한 논의는 [이 이슈](https://github.com/vuejs-templates/webpack/pull/347)와 [이 이슈](https://github.com/vuejs-templates/webpack/pull/296#discussion_r82073211)를 읽어보세요. |
| 17 | + |
| 18 | +하지만 매번 vuex와 vue-router를 추가하는 것은 매우 까다로운 일이기 때문에 템플릿을 만들었습니다. [이 저장소](https://github.com/ChangJoo-Park/webpack)를 살펴보세요. 간단한 사용법은 아래에 있습니다. 수정하려면 fork 하여 사용하시면 됩니다. 현재 standard와 airbnb 스타일을 모두 지원하고 있습니다. |
| 19 | + |
| 20 | +```terminal |
| 21 | +$ npm install -g vue-cli |
| 22 | +$ vue init changjoo-park/webpack my-project |
| 23 | +$ cd my-project |
| 24 | +$ npm install |
| 25 | +$ npm run dev |
| 26 | +``` |
| 27 | + |
| 28 | +자신만의 템플릿을 만드는 것은 [사용자 정의 템플릿 작성해보기](https://github.com/vuejs-kr/vue-cli#사용자-정의-템플릿-작성해보기)를 읽어보세요 |
| 29 | + |
| 30 | +## vue-router 와 vuex |
| 31 | + |
| 32 | +### 프로젝트 구조 |
| 33 | + |
| 34 | +위 템플릿으로 만든 `my-project`를 살펴보겠습니다. |
| 35 | + |
| 36 | + |
| 37 | + |
| 38 | +vue-cli로 만든 `my-project` 앱의 `src` 디렉터리의 구조입니다. 추가된 것은 `routes.js`와 `vuex` 디렉터리입니다. |
| 39 | + |
| 40 | +### vue-router |
| 41 | + |
| 42 | +`main.js` 를 살펴보겠습니다. |
| 43 | + |
| 44 | +```javascript |
| 45 | +// The Vue build version to load with the `import` command |
| 46 | +// (runtime-only or standalone) has been set in webpack.base.conf with an alias. |
| 47 | +import Vue from 'vue' |
| 48 | +import Router from 'vue-router' |
| 49 | +import App from './App' |
| 50 | +import routes from './routes' |
| 51 | +// Styles |
| 52 | +import './assets/styles/styles.scss' |
| 53 | + |
| 54 | +Vue.use(Router) |
| 55 | +const router = new Router({ |
| 56 | + mode: 'history', |
| 57 | + scrollBehavior: () => ({ y: 0 }), |
| 58 | + routes |
| 59 | +}) |
| 60 | + |
| 61 | +/* eslint-disable no-new */ |
| 62 | +new Vue({ |
| 63 | + router, |
| 64 | + ...App |
| 65 | +}).$mount('#app') |
| 66 | +``` |
| 67 | + |
| 68 | +중요한 부분은 **4번**, **6번**, **10번 ~ 15번**, **19번** 코드 입니다. |
| 69 | + |
| 70 | +vue-router와 라우트에 대한 내용(routes.js)을 가져오고(**4번**, **6번**), Vue에 vue-router를 사용할 것을 알려줍니다.(**10번**) 그리고 router 인스턴스를 만든 후(**11번** ~ **15번**) 이 것을 실제 사용할 Vue에 넣어 인스턴스를 만듭니다.(**19번**) |
| 71 | + |
| 72 | +**11번** ~ **15번** 코드의 `mode`나 `scrollBehavior`는 공식 문서의 [HTML5 히스토리 모드](https://router.vuejs.org/kr/essentials/history-mode.html)와 [스크롤 동작](https://router.vuejs.org/kr/advanced/scroll-behavior.html)을 읽어보세요. |
| 73 | + |
| 74 | +`main.js` 파일에서 살펴보지 않은 `routes.js` 파일을 살펴보겠습니다. |
| 75 | + |
| 76 | +```javascript |
| 77 | +import HomePage from 'components/HomePage' |
| 78 | +import CounterPage from 'components/CounterPage' |
| 79 | + |
| 80 | +export default [ |
| 81 | + { |
| 82 | + path: '/', |
| 83 | + name: 'home-page', |
| 84 | + component: HomePage |
| 85 | + }, |
| 86 | + { |
| 87 | + path: '/counter', |
| 88 | + name: 'counter-page', |
| 89 | + component: CounterPage |
| 90 | + }, |
| 91 | + { |
| 92 | + path: '*', |
| 93 | + redirect: '/' |
| 94 | + } |
| 95 | +] |
| 96 | +``` |
| 97 | + |
| 98 | +앞에서 설명하지 않았지만 `changjoo-park/webpack` 템플릿에는 **HomePage** 와 **CounterPage** 컴포넌트가 포함되어 있습니다. |
| 99 | + |
| 100 | +`path`와 `name` 그리고 `component`를 지정해주면 해당 `path`에 맞는 컴포넌트를 화면에 그려줍니다. 지정하지 않은 `path`를 걸러내기 위해 맨 아래 라우트를 추가하였습니다. 자세한 내용은 공식 vue-router 가이드의 [시작하기](https://router.vuejs.org/kr/essentials/getting-started.html)를 보시면 됩니다. |
| 101 | + |
| 102 | +`App.vue` 단일 파일 컴포넌트의 템플릿을 보겠습니다. |
| 103 | + |
| 104 | +```html |
| 105 | +<template> |
| 106 | + <div id="app"> |
| 107 | + <ul class="navigation"> |
| 108 | + <li><router-link v-bind:to="{ name: 'home-page' }">Home</router-link></li> |
| 109 | + <li><router-link v-bind:to="{ name: 'counter-page' }">Counter</router-link></li> |
| 110 | + </ul> |
| 111 | + <router-view></router-view> |
| 112 | + </div> |
| 113 | +</template> |
| 114 | +``` |
| 115 | + |
| 116 | +`App.vue` 템플릿에서 보셔야할 부분은 `router-link`와 `router-view` 입니다. `router-link`는 라우트를 지정해주고, `router-view`는 지정된 라우트에 맞는 컴포넌트를 화면에 그려줍니다. |
| 117 | + |
| 118 | +#### 새 페이지를 추가하려면 |
| 119 | + |
| 120 | +새 페이지를 추가하는 방법은 다음 순서로 하시면 됩니다. |
| 121 | + |
| 122 | +- 새 페이지를 위한 컴포넌트를 만듭니다. |
| 123 | +- `routes.js`에 라우트를 정의해 줍니다. |
| 124 | +- 새로 만든 페이지로 이동할 수 있도록 `router-link`를 만듭니다. |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | +### vuex |
| 129 | + |
| 130 | +vuex 는 앱 전체에서 공유할 수 있는 데이터센터 입니다. 부모 컴포넌트와 자식 컴포넌트 사이에 데이터를 주고 받을 때 불편함을 해소하는데 큰 역할을 합니다. |
| 131 | + |
| 132 | +vuex 디렉터리 구조 입니다. |
| 133 | + |
| 134 | + |
| 135 | + |
| 136 | +vuex의 자세한 내용은 [vuex 시작하기](https://vuex.vuejs.org/kr/intro.html)를 읽고 보시면 더 이해가 쉽습니다. vuex의 `main.js`파일에 해당하는 `store.js` 부터 살펴보겠습니다. |
| 137 | + |
| 138 | +```javascript |
| 139 | +import Vue from 'vue' |
| 140 | +import Vuex from 'vuex' |
| 141 | +import * as actions from './actions' |
| 142 | +import * as getters from './getters' |
| 143 | +import modules from './modules' |
| 144 | + |
| 145 | +Vue.use(Vuex) |
| 146 | + |
| 147 | +export default new Vuex.Store({ |
| 148 | + actions, |
| 149 | + getters, |
| 150 | + modules, |
| 151 | + strict: true |
| 152 | +}) |
| 153 | +``` |
| 154 | + |
| 155 | +vue-router를 추가하는 것과 동일한 흐름으로 이해하시면 됩니다. vuex 구조에서 `actions`, `getters`(state), `modules`(mutations)를 Vuex의 `store`에 추가하여 vuex인스턴스를 만드는 코드입니다. |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | + |
| 160 | + |
| 161 | +우선 `mutation-types.js`를 보겠습니다. vuex가 어떤 종류의 `mutation`을 정의한 파일입니다. |
| 162 | + |
| 163 | +```javascript |
| 164 | +export const DECREMENT_MAIN_COUNTER = 'DECREMENT_MAIN_COUNTER' |
| 165 | +export const INCREMENT_MAIN_COUNTER = 'INCREMENT_MAIN_COUNTER' |
| 166 | +``` |
| 167 | + |
| 168 | +카운터를 증가시키고 감소시키는 조작만 있으면 됩니다. |
| 169 | + |
| 170 | +이제 실제 조작에 대한 구현을 보겠습니다. `counters.js` 파일입니다. modules 디렉터리의 `index.js`는 modules에 있는 module을 export 해주는 유틸 스크립트입니다. 한번 살펴보세요. |
| 171 | + |
| 172 | +```javascript |
| 173 | +import * as types from '../mutation-types' |
| 174 | + |
| 175 | +const state = { |
| 176 | + main: 0 |
| 177 | +} |
| 178 | + |
| 179 | +const mutations = { |
| 180 | + [types.DECREMENT_MAIN_COUNTER] (state) { |
| 181 | + state.main-- |
| 182 | + }, |
| 183 | + [types.INCREMENT_MAIN_COUNTER] (state) { |
| 184 | + state.main++ |
| 185 | + } |
| 186 | +} |
| 187 | + |
| 188 | +export default { |
| 189 | + state, |
| 190 | + mutations |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +`state`가 있고, `mutations`가 있습니다. state는 실제 counter의 상태를 보관합니다. 그리고 mutations는 위에서 정의한 조작 요청이 오면 카운터를 증가 / 감소 시킵니다. |
| 195 | + |
| 196 | +mutation을 실제로 발생시키려면 action이 필요합니다. action은 vue 컴포넌트에서 `dispatch`를 이용해 호출하는 메소드입니다. |
| 197 | + |
| 198 | +```javascript |
| 199 | +import * as types from './mutation-types' |
| 200 | + |
| 201 | +export const decrementMain = ({ commit }) => { |
| 202 | + commit(types.DECREMENT_MAIN_COUNTER) |
| 203 | +} |
| 204 | + |
| 205 | +export const incrementMain = ({ commit }) => { |
| 206 | + commit(types.INCREMENT_MAIN_COUNTER) |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +`actions`는 백엔드 API와 통신하는 역할을 가지지만 여기서는 백엔드가 없는 앱이므로 바로 mutation을 `commit`을 이용해 요청합니다. |
| 211 | + |
| 212 | +이제 컴포넌트에서 사용할 상태를 호출하기 위한 `getters`(state) 를 살펴보겠습니다. 파일은 `getters.js` 입니다. |
| 213 | + |
| 214 | +```javascript |
| 215 | +export const mainCounter = state => state.counters.main |
| 216 | +``` |
| 217 | + |
| 218 | +템플릿에 포함된 애플리케이션은 Counter 앱에 대한 상태만 보관하므로 **mainCounter**라는 **getter**만 있습니다 .state의 counters의 main을 반환하는 역할을 합니다. 이 곳에 `filter`나 `map`등을 이용해 원하는 내용만 사용할 수 있습니다. |
| 219 | + |
| 220 | +vuex가 준비되었으므로 counter 앱을 만들어보겠습니다. 가장 먼저, App 컴포넌트에 vuex를 사용한다고 알려줘야 합니다. |
| 221 | + |
| 222 | +`App.vue`의 `script` 블럭입니다. |
| 223 | + |
| 224 | +````javascript |
| 225 | +<script> |
| 226 | +import store from 'src/vuex/store' |
| 227 | + |
| 228 | +export default { |
| 229 | + store, |
| 230 | + name: 'app' |
| 231 | +} |
| 232 | +</script> |
| 233 | +```` |
| 234 | + |
| 235 | +vuex의 store를 가져와 App 컴포넌트에 추가했습니다. 이제 앱의 어디든 `this.$store`를 이용해 vuex에 접근할 수 있습니다. |
| 236 | + |
| 237 | +`CounterPage.vue` 파일에 카운터 앱이 있습니다. 따로 컴포넌트로 분리해 보세요. 템플릿을 만들기 쉽게하기 위해 `CounterPage.vue`에 넣은 것 입니다. 코드를 살펴보겠습니다. |
| 238 | + |
| 239 | +템플릿 코드 입니다. |
| 240 | + |
| 241 | +```html |
| 242 | +<div class="counter-number"> |
| 243 | + {{counter}} |
| 244 | +</div> |
| 245 | +<button class="counter-button" v-on:click="incrementCounter">+</button> |
| 246 | +<button class="counter-button" v-on:click="decrementCounter">-</button> |
| 247 | +``` |
| 248 | + |
| 249 | +스크립트 코드입니다. |
| 250 | + |
| 251 | +```javascript |
| 252 | +computed: { |
| 253 | + counter () { |
| 254 | + return this.$store.getters.mainCounter |
| 255 | + } |
| 256 | +}, |
| 257 | +methods: { |
| 258 | + incrementCounter () { |
| 259 | + this.$store.dispatch('incrementMain') |
| 260 | + }, |
| 261 | + decrementCounter () { |
| 262 | + this.$store.dispatch('decrementMain') |
| 263 | + } |
| 264 | +} |
| 265 | +``` |
| 266 | + |
| 267 | +`computed`에 `getters`에 지정해 놓은 **mainCounter**를 가져왔습니다. 그리고 `mehotds`에는 `actions`에 추가한 액션들을 호출하는 코드가 있습니다. action을 실행하는 것은 `dispatch`입니다. 자세한 내용은 [Vuex가이드의 액션](https://vuex.vuejs.org/kr/actions.html)을 읽어보세요. |
| 268 | + |
| 269 | +#### 새 모듈을 추가하려면 |
| 270 | + |
| 271 | +- mutation-types.js 에 어떤 변이가 필요한지 추가하세요. |
| 272 | +- modules 디렉터리에 사용할 모듈 파일을 만들고, state와 mutations을 추가하세요. |
| 273 | +- `actions.js`에 mutation을 호출할 메소드를 만드세요. |
| 274 | +- `getter.js`에 컴포넌트에서 사용할 상태를 추가하세요. |
| 275 | +- 컴포넌트에서 `computed`에 상태를, `methods`에 액션을 호출할 `dispatch`를 추가하세요. |
| 276 | + |
| 277 | +[vuex의 가이드](https://vuex.vuejs.org/kr/intro.html)에 더 자세한 내용이 있습니다. 꼭 읽어보세요. |
0 commit comments