티스토리 뷰

Vuex를 번외편으로 총 6부작 생각하고 시작한 라이브러리 설명을 벌써 4부를 마무리하고 이제 다섯번째 Actions 를 시작하려고 하니 급 아련한 느낌이 들었다.

 

사실 하다가 관둘줄 알았거든. 내용이 길어서 이거 끝까지 설명할 수 있으려나 생각을 했는데 다음시간이면 Vuex내용 중 중요한 부분들은 다 끝난다니 다행스럽다고 생각한다. 그러니 이제 조금 더 달려서 Nuxt.js 로 돌아가도록 하자. 난 Nuxt.js 초급부분을 한 10편이면 끝날줄 알고 막 달리는데 번외편까지 생기고 공부할게 한두가지가 아니라서 벌써 15편째 연재 중인데 곰곰이 생각해보니까 Nuxt.js는 얼마 다루지도 않은 것 같아서 앞으로 한참 더 달려야겠다고 생각을 하니.. 아 지겹...........지 않아. 더 열심히 해야지.

 

자 오늘은 Vuex의 대표 옵션 Actions에 대해서 알아볼거다.

가만보면 Actions와 저번시간에 배웠던 Mutations가 꽤 비슷한걸? 이라는 인상을 받겠지만 가이드 문서에 따르면 아주 중요한 부분에서 이 두개의 옵션 기능 차이가 있다. Mutations는 저번시간에 이야기한 대로 "동기"방식의 데이터 처리만 가능하다는 것에 반해 Actions는 "비동기" 방식의 데이터처리가 가능한 부분인 것이다.

 

아직도 동기 / 비동기 방식에 대한 이해가 없다면 링크를 따라가 공부를 더 하고 오길 바란다.(https://blog.thereis.xyz/72)

 

암튼 이런 이유로 Actions는 Mutations와 같이 데이터를 변이 시킬 수 있지만 Mutations은 "동기"방식, Actions는 "비동기" 방식이라는 점. 잊지말자.

 

자 샘플 코드를 만들어볼까

 

App.vue

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

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

Vue.use(Vuex)

const store = 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 ){
            setTimeout( function(){
                context.commit("addTodos", [
                    { id: 1, text: '출근하기', done: true },
                    { id: 2, text: '아침밥 몰래 먹기', done: false },
                    { id: 3, text: '점심시간까지 시간 보내기', done: false }
                ])
            }, 10000 )
        }
    }
})

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

일단 코드의 특징은 기존의 state의 todos에 데이터를 없애버리고 그 대신 mutations에 addTodos 메서드를 추가해서 파라미터로 넘어온 list 객체를 state의 todos에 밀어넣어주는 역할을 수행하게 만들었다. 그리고 actions 옵션을 추가하고 그 곳에서 getTodoList 메서드를 이용해 store의 인스턴스인 "context"를 파라미터로 받아서 비동기 처리의 예를 보여주기 위해 10초 후에 데이터를 "addTodos"를 통해 커밋해주는 구조를 갖고 있다.

 

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 { mapState } from 'vuex';

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

            if( self.isFilter) {
                list = getters.doneTodos
            } else {
                list = state.todos
            }

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

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

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

UI 에는 카운터를 표시할 수 있는 코드와 데이터를 가져오는 버튼을 하나 추가했다. 버튼을 클릭하면 onGetListClick이라는 메서드가 실행되는데 이때 $store객체를 통해 Actions의 메서드를 실행시킬 수 있는 "dispatch" 메서드를 통해 "getTodoList" 라는 Actions의 메서드를 싱행한다.

위에서 코딩한 것에 따르면 데이터를 가져오는데 10초가 걸린다니 counter 를 1초에 1씩 감소하는 것을 화면에 표시하도록 했다.

 

위의 코드를 실제로 동작하는 것을 확인하면

 

Actions를 어떻게 다뤄야할지 잘 모르겠는가? 아니면 "아니, 왜 궂이 이해하기 어렵게 Actions를 쓰면서 비동기처리를 하는거지??" 라고 생각이 드는가? 전자의 경우엔 코드가 점점 복잡해지고 메서드 상호간 서로 엮이면서 흐름이 복잡해지니 어려워하는 것은 어쩔 수 없다. 이런 경우엔 위의 코드를 잘 따라가서 확인해보고 직접 따라서 하다보면 어떻게 다뤄야할지 금방 이해 할 수 있을 거라고 생각이 된다.

 

후자의 경우엔 "궂이 Vue.js를 쓰는 이유가 무엇입니까?"라고 되물어보겠다. Vuex를 활용하는 이유는 이를 통해 개발 생산성을 높이기 위함인데 이런 과정에서 불가피하게 발생하는 학습곡선(Learning Curve)을 넘기 싫다면 이런 툴을 활용할 이유가 없다는게 내 생각이다.

 

자, Actions에서 위와같이 비동기 코드를 수행할 수 있다면 아래 처럼 코드를 고쳐 쓸 수 있다.

 

App.vue

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 )
        });
    }
}

hello.vue

onGetListClick: function() {
    var self = this;
    var counterInterval;
    self.$store.dispatch("getTodoList").then(() => {
        alert("process complete!");
    });

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

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

actions를 설정할 때 메서드가 Promise 인스턴스를 반환하면 dispatch하는 쪽에서 then을 이용해 처리가 완료된 이후의 이벤트 핸들러를 활용할 수 있다.

 

그리고 Promise ~ Then을 활용할 수 있다면 이런식으로도 활용이 가능하다는 이야기가 된다.

onGetListClick: async function() {
    var self = this;
    var counterInterval;

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

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

    await self.$store.dispatch("getTodoList");
    alert("process complete!");
}

이렇게 Async - Await 방식으로 코딩을 해도 정상적으로 동작하는 것을 확인할 수 있다.

 

오늘은 Actions를 활용하여 비동기 코드를 처리하는 법을 배웠다.

이제 Vuex의 마지막 Modules에 대해서 공부할 내용만 남았다. 

 

Modules는 Vuex의 저장소 내용의 양이 많아져 데이터 성격에 따라, 혹은 컨포넌트에 따라 모듈별로 분류를 해야할 때 활용할 수 있는 내용이다. 내용이 조금 어려워 보이니 정신차리고 Vuex를 마무리 지어보자.

 

오늘은 이만 안녕!

댓글
댓글쓰기 폼