티스토리 뷰

서론 : 왜 Mutation을 사용하는가

이제 Vuex의 네 번째 챕터 Mutation(변이)에 대한 이야기를 할 시간이 왔다.

Mutation는 말 그대로 상태(state) 값을  변경하는 것으로 이해하면 된다. 다만 주의할 점이 Mutation의 상태 값의 변경에 대해서 "동기"방식으로 처리할 때 사용해야 한다는 점이다. 여기서 동기, 비동기라는 말이 헷갈린다고 생각하시는 분들을 위해 동기 방식 / 비동기 방식에 대해서 설명을 확인하고 돌아오자.(https://blog.thereis.xyz/72)

 

사실 변이라는 것은 별게 아니다. 그냥 state 값을 변경하는 것인데 mutations라는 옵션에서 메서드를 통해 값의 변경을 가하는 것 뿐이다. 그렇다면 이렇게 생각할 수도 있다. "그냥 state 값을 변경하면 왜 안되는데?"라고 말이지.

일단 중간에 이런 값을 핸들링하는 함수를 따로 분리해 놓는 것은 일단 state의 값을 직접 접근하여 그냥 변경하게 되면 코드가 분산되기 쉬워진다.(어떻게 아냐고? 경험이지 경험. 엣헴) 분산된 코드는 추적하기 힘들어지고 힘들어진 코드는 스파게티가 되어버린다. 그래서 mutations 옵션을 따로 두어 state 값의 변이를 만들어주는 것이다.

스파게티 코드의 예. 코드를 수정할 방법이 없다.

 

 

본론#1 : 준비운동

이제 Mutation을 배워보도록 하자.

일단 지난번 코드에서 다시 아래의 코드로 수정해보도록하자.

 

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: [
            { id: 1, text: '출근하기', done: true },
            { id: 2, text: '아침밥 몰래 먹기', done: false },
            { id: 3, text: '점심시간까지 시간 보내기', done: false }
        ]
    },
    getters: {
        doneTodos: function( state ) {
            return state.todos.filter( function( todo ){
                return todo.done
            })
        }, findTodoById: (state) => (id) => state.todos.find(todo => todo.id === id) 
    }
})

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

hello.vue

<template>
    <div>
        <h1>Todo List</h1>
        <div>
            <button v-on:click="onBtnClick">{{getBtnText}}</button>
        </div>
        <hr />
        <ul>
            <li v-for="item in todoList" v-bind:key="item.id">
                {{item.text}}
            </li>
        </ul>
    </div>
</template>
<script>
import { mapState } from 'vuex';

export default {
    name: 'HelloWorld'
    , data: function() {
        return {
            isFilter: false
        }
    }
    , 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;
        }
    }
}
</script>

 

위의 코드를 보면 아직 Mutations를 적용하지 않았다는 것을 알 수 있다. 일단 테스트 코드를 만들기 위해 먼저 짜둔 테스트 코드를 정리한 것이니 뭐지? 하고 생각 말고 다음 코드를 작성해보자.

 

본론#2 : 시작

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: [
            { id: 1, text: '출근하기', done: true },
            { id: 2, text: '아침밥 몰래 먹기', done: false },
            { id: 3, text: '점심시간까지 시간 보내기', done: false }
        ]
    },
    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, item ){
            item.done = !item.done;
        }
    }
})

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

mutations 항목을 잘 보면 changeDoneState라는 함수를 하나 추가해줬다. 이 함수는 첫 번째 파라미터로 store의 state 값을 받아오고 두 번째로 item 값을 받아오게 되어있다. 이 item 값은 todos의 선택된 하나의 객체로 이 아이템의 done 값을 토글 처리해주는 역할을 수행한다. (state값은 vuex가 알아서 넣어준다.)

 

hello.vue

<template>
    <div>
        <h1>Todo List</h1>
        <div>
            <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
        }
    }
    , 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("changeDoneState", item)
        }
    }
}
</script>

HTML 부분을 보면 각 항목마다 버튼이 생겼고 item의 done 값에 따라서 "완료", "미완료"를 노출하게 되어있다.

그리고 클릭을 할 때마다 "onMutationClick" 메서드를 실행하는데 이때 "item" 값을 넘겨주게 된다.

 

methods 옵션에 있는 onMutationClick 함수를 확인해보자. 일단 위에서 본 것 같이 item을 파라미터로 받아오고 store의  내장 함수인 commit을 실행한다. commit은 store 내부에 있는 mutations 옵션에 접근해 파라미터로 넘겨준 함수명과 동일한 함수를 실행시켜준다. 위와 같이 코드를 작성하면 아래와 같이 동작하는 것을 확인할 수 있다.

mutation이 어떻게 동작하는지 이해가 됐으리라고 믿는다. 진짜 믿는다. 모르겠으면 몇 번 따라 해 보면 이해가 될 거다. 다음으로는 commit 함수를 아래와 같이 실행할 수도 있다는 점도 확인하면 좋다.

 

App.vue

mutations: {
    changeDoneState: function( state, payload ){
        payload.item.done = !payload.item.done;
    }
}

hello.vue

onMutationClick: function(item){
    this.$store.commit("changeDoneState", {
        item: item
    })
}

자, 위와 같은 payload 방식도 사용할 수 있다는 점과 아래와 같이 commit을 실행할 수도 있다는 것을 알아두자.

 

hello.vue

onMutationClick: function(item){
    this.$store.commit({
        type: "changeDoneState", 
        item: item
    })
}

참고로 내 개인적인 취향은 세 번째 방법을 선호한다. 뭔가 깔끔해 보여서.

 

저 메서드의 이름은 상수로 먼저 선언한 뒤에 이용하는 방법도 있는데 이를 이용하여 대규모 개발 시 유용하게 사용할 수 있다. 다만 가이드 문서에서는 "이는 완전히 선택사항"이라고 되어있다. 그러므로 원하는 사람만 그렇게 작성하도록 하자.

 

오늘은 Vuex의 mutations에 대해서 배워봤다.

mutations는 반드시 "동기"환경에서만 사용하라고 했다. 그렇다면 "비동기"환경에서는 어떻게 해야 할까.

다음 시간에 "Actions"를 통해 비동기 환경에서의 Vuex를 활용하는 법에 대하여 배워보도록 하자.

 

안녕!

댓글
댓글쓰기 폼