티스토리 뷰

Nuxt.js의 Store기능을 익히기 위해 Vuex를 공부한지 어언 2주. 이제 마지막 Modules에 대해서 알아볼 시간이 도래하였다. 

Modules의 의도를 아주 간단히 풀어보자면 우리가 다운로드 받는 파일들을 하드디스크에 저장할 때 바탕화면에 계속 저장한다고 해보자.

혼란한 마음을 감출 수 없는 바탕화면

위의 이미지와 같이 혼란함이 가득해질 것이다. Store객체도 마찬가지. 웹 어플리케이션에서 상태를 저장할 일이 얼마나 많은데 이것 저것 분류해서 넣어놓지 않으면 코드양이 늘어나면 늘어날 수록 Store객체의 속은 혼란함 만이 가득하게 될 것이다.

 

우리는 저 복잡해진 바탕화면을 정리하기 위해 폴더를 만들고 비슷하거나 연관되어있는 파일들을 정리해 담는 작업을 한다. 이때 만드는 폴더가 바로 Vuex에서 "Modules"에 해당한다고 생각하면 될 것이다.

어플리케이션이 점점 더 복잡해질 수록 비슷한 역할, 카테고리 등을 나누어 각 모듈별로 State, Getters, Mutations, Actions를 만들고 데이터를 저장해서 필요할 때 햇깔리지 않도록 적절히 분류하는 작업이 필요하게 된다. 그리고 각 모듈별로 서로 상호작용하여 데이터를 업데이트 할 수 있어야 한다.

 

Vuex의 Modules 를 한번 시작해보자.

 

아, 그전에 이번 Modules를 위해서 store.js 파일을 만들어서 그전에 App.vue에 있는 Vuex.store의 인스턴스 객체를 옮겨주고 vue create 명령으로 생성된 main.js 쪽에 import 해주는 방식으로 바꿔줄거다. 그러니 그 전 소스코드로 작업하던 사람들은 아래의 코드를 잘 보고 한땀 한땀 따라가도록 하자.

 

store.js 파일은 main.js 파일과 같은 위치에 생성한다.

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        todos: []
    },
    getters: {
        doneTodos: function( state ) {
            return state.todos.filter( function( todo ){
                return todo.done
            })
        }, findTodoById: (state) => (id) => state.todos.find(todo => todo.id === id) 
    },
    mutations: {
        changeDoneState: function( state, payload ){
            payload.item.done = !payload.item.done;
        },
        addTodos: function( state, list ){
            state.todos = list;
        }
    },
    actions: {
        getTodoList: function( context ){
            return new Promise((resolve) => {
                setTimeout( function(){
                    context.commit("addTodos", [
                        { id: 1, text: '출근하기', done: true },
                        { id: 2, text: '아침밥 몰래 먹기', done: false },
                        { id: 3, text: '점심시간까지 시간 보내기', done: false }
                    ])

                    resolve()
                }, 10000 )
            });
            
        }
    }
})

main.js

import 'es6-promise/auto'
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

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

App.vue

<template>
  <div id="app">
    <HelloWorld></HelloWorld>
  </div>
</template>

<script>
import Vue from 'vue'
import HelloWorld from './components/hello'

export default {
    name: 'app'
    , components: {
        HelloWorld
    }
}
</script>

 

위와같이 코드를 수정하고 브라우저로 화면을 확인해본다면 그 전과 동일하게 동작하는 것을 확인할 수 있을 것이다.

그럼 이제 store.js를 수정하여 modules를 사용해 보도록 하자.

 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const todoModule = {
    namespaced: true,
    state: {
        todos: []
    },
    getters: {
        doneTodos: function( state ) {
            return state.todos.filter( function( todo ){
                return todo.done
            })
        }, findTodoById: (state) => (id) => state.todos.find(todo => todo.id === id) 
    },
    mutations: {
        changeDoneState: function( state, payload ){
            payload.item.done = !payload.item.done;
        },
        addTodos: function( state, list ){
            state.todos = list;
        }
    },
    actions: {
        getTodoList: function( context ){
            return new Promise((resolve) => {
                setTimeout( function(){
                    context.commit("addTodos", [
                        { id: 1, text: '출근하기', done: true },
                        { id: 2, text: '아침밥 몰래 먹기', done: false },
                        { id: 3, text: '점심시간까지 시간 보내기', done: false }
                    ])

                    resolve()
                }, 10000 )
            });
            
        }
    }
}

export default new Vuex.Store({
    modules: {
        todo: todoModule
    }
})

 

위의 코드에서 잘 살펴봐야할 것은 기존에 Vuex.Store에 속해있던 내용들이 todoModule이라는 이름의 객체에 옮겨갔고 Vuex.Store 의 인스턴스를 생성할 때 "modules" 옵션에 "todo"라는 객체에 할당되었다는 것과 "namespaced: true" 옵션이 추가로 todoModule에 포함되었다는 것이다. 이 옵션은 나중에 외부에서 이 todoModule에 접근해 actions의 함수를 실행한다거나 mutations의 함수를 실행할때 네임스페이스를 통해 사용한다는 옵션이다. 이 옵션은 외부에서 모듈 객체로 접근할 때 매우 중요하므로 포함시켜주는 것을 잊지말자. 혹시 네임스페이스 옵션을 깜빡하게 되면 getters나 mutations의 함수들이 모듈명으로 구분되는 것이 아니라 하나로 짬뽕이 되는 사태가 벌어지면서 혼란을 야기시키니 꼭꼭 잊지말자.

 

그러면 hello.vue 파일을 아래와 같이 수정하자.

 

hello.vue

<template>
    <div>
        <h1>Todo List</h1>
        <div>
            <p>
                카운터 : {{counter}}
            </p>
            <button v-on:click="onGetListClick">목록 가져오기</button>
            <button v-on:click="onBtnClick">{{getBtnText}}</button>
        </div>
        <hr />
        <ul>
            <li v-for="item in todoList" v-bind:key="item.id">
                <span>{{item.text}}</span>&nbsp;
                <button v-on:click="onMutationClick(item)">
                    <span v-if="item.done">완료</span>
                    <span v-if="!item.done">미완료</span>
                </button>
            </li>
        </ul>
    </div>
</template>
<script>
import store from '../store';

export default {
    name: 'HelloWorld'
    , data: function() {
        return {
            isFilter: false
            , counter: 10
        }
    }
    , computed: {
        todoList: function() {
                var list, self = this

                if( self.isFilter) {
                    list = store.getters['todo/doneTodos']
                } else {
                    list = store.state.todo.todos
                }

                return list;
            }, getBtnText: function() {
                var message, self = this
                message = self.isFilter ? "전체 목록 보기" : "완료된 목록만 보기"

                return message
            }
    }, methods: {
        onBtnClick: function(){
            this.isFilter = !this.isFilter;
        }, onMutationClick: function(item){
            store.commit({
                type: "todo/changeDoneState", 
                item: item
            })
        }, onGetListClick: async function() {
            var self = this;
            var counterInterval;
            
            counterInterval = setInterval( function() {
                self.counter--;

                if( self.counter === 0 ){
                    clearInterval( counterInterval );
                    self.counter = 10;
                }
            }, 1000);

            await store.dispatch("todo/getTodoList");
            alert("process complete!");
        }
    }
}
</script>

일단 전체 코드를 먼저 공개하고 한줄 한줄 보면서 코드를 수정하길 바라지만 사실 고쳐야 할 곳이 많지 않으므로 script 부분 중 고쳐야 할 곳만 따로 분리해서 설명하도록 하겠다.

 

import store from '../store';

store를 별도의 js 파일로 분리하여 사용하고 있으므로 이렇게 store 인스턴스를 import해서 사용할 수 있다. store 객체는 이미 인스턴스화 되어 Vue가 인스턴스화 될 때 같이 포함되어 사용되고 있으므로 다른 컴포넌트에서도 동일하게 이용할 수 있다.

todoList: function() {
    var list, self = this

    if( self.isFilter) {
        list = store.getters['todo/doneTodos']
    } else {
        list = store.state.todo.todos
    }

    return list;
}

위의 코드에서 computed 부분이다. 위의 함수는 isFilter라는 data의 값에 따라서 getters의 필터링된 목록을 가져올지, 아니면 state의 목록을 가져올지 결정하는 함수다. 위에서 import된 store 덕분에 이제부터는 store 객체를 통해 vuex 인스턴스에 접근할 수 있어서 코드를 수정한 부분이 있고, isFilter가 false일 때 실행하는 구문(아래 쪽)부터 보면 state에 접근할 때 중간에 모듈 이름이 추가된 것을 확인 할 수 있다.

그리고 isFilter가 ture일 때 실해하는 구문(윗 쪽)을 보면 이 것은 getters에 접근하는 방법인데 아까 우리가 namespaced 옵션에 true를 부여한 것을 기억해내야한다. 그리고 todo라는 이름의 모듈을 통해 doneTodos라는 getters 함수에 접근하는 방법으로 위와 같이 "모듈명/getter 함수명"으로 네임스페이스 방식으로 접근한다.

 

onMutationClick: function(item){
    store.commit({
        type: "todo/changeDoneState", 
        item: item
    })
}, onGetListClick: async function() {
    var self = this;
    var counterInterval;

    counterInterval = setInterval( function() {
        self.counter--;

        if( self.counter === 0 ){
            clearInterval( counterInterval );
            self.counter = 10;
        }
    }, 1000);

    await store.dispatch("todo/getTodoList");
    alert("process complete!");
}

mutations 함수를 실행하기 위해서 commit을 실행할 때도 위와 같이 "모듈명/함수명" 이렇게 접근해 사용하도록 한다. actions도 마찬가지로 네임스페이스 방식으로 접근해서 사용한다.

 

Vuex 가이드 문서를 보면 modules를 중첩해서 사용하는 방식도 설명되어있지만 개인적으로는 중첩해서 사용할 일이 있겠냐고 생각이 들긴 하지만 알아둘 것은 위의 네임스페이스 방식을 이용하면 사용하는데는 큰 문제는 없을 것 같지만 혹시 모르니까 아주 간단하게 예제 코드를 만들어 확인해 보도록하자.

store.js

const todoModule = {
    namespaced: true,
    state: {
        todos: []
    },
    getters: {
        doneTodos: function( state ) {
            return state.todos.filter( function( todo ){
                return todo.done
            })
        }, findTodoById: (state) => (id) => state.todos.find(todo => todo.id === id) 
    },
    mutations: {
        changeDoneState: function( state, payload ){
            payload.item.done = !payload.item.done;
        },
        addTodos: function( state, list ){
            state.todos = list;
        }
    },
    actions: {
        getTodoList: function( context ){
            return new Promise((resolve) => {
                setTimeout( function(){
                    context.commit("addTodos", [
                        { id: 1, text: '출근하기', done: true },
                        { id: 2, text: '아침밥 몰래 먹기', done: false },
                        { id: 3, text: '점심시간까지 시간 보내기', done: false }
                    ])

                    resolve()
                }, 10000 )
            });
            
        }
    },
    modules: {
        check: {
            state: {
                count: 5
            },
            getters: {
                getCount: function(state) {
                    return state.count
                }
            },
            mutations: {
                increase: function( state ) {
                    state.count++;
                }
            }
        }
    }
}

위와 같이 모듈 안에 모듈을 추가하면 아래처럼 접근하면 state와 각 함수를 실행할 수 있다.

 

console.log( store.state.todo.check.count ) // 5
console.log( store.getters['todo/getCount']) // 5
store.commit({
    type: 'todo/increase'
})

console.log( store.state.todo.check.count ) // 6

state의 경우에는 "store.state.모듈명.모듈명.state 객체" 방식으로 접근하는 반면 나머지 getters나 mutations 같은 나머지 옵션들은 상위 모듈안에 함수가 포함되는 것을 확인할 수 있다. 동일한 함수명이 있는 경우 상위 모듈 메서드 부터 차례대로 실행되는 것을 확인 할 수 있다.

 

Modules 기능은 위의 내용 외에도 상당히 다양한 기능들을 제공하고 있지만 Modules에 관련된 내용은 이것으로 정리하려고 한다. 일단 Modules에서 가장 중요한 내용은 모듈로 분리된 내용을 어떻게 접근하여 사용하느냐는 부분이고 이 부분들에 대해서는 대략적인 설명은 마쳤다고 생각하기 때문이다.

 

그 외에 mapState, mapGetters, mapActions와 같은 헬퍼를 활용하는 방법이라던지, 동적으로 모듈을 추가한다던지 하는 내용들도 있지만 이런 내용들은 Nuxt.js 를 설명하면서 더 많은 내용들을 알아보도록 하자.

 

오늘은 여기까지! 안녕!

 

댓글
댓글쓰기 폼