티스토리 뷰

반응형
Vuex는 Vue.js 애플리케이션에서 사용할 수 있는 상태관리 라이브러리입니다. 애플리케이션에 존재하는 모든 컴포넌트들에서 접근 가능한 중앙 집중식 데이터 저장소이고, 이를 통해 모든 데이터의 흐름을 중앙에서 관리할 수 있습니다. 

설치방법


vuex를 사용하고자하는 프로젝트 경로에 가서 아래 명령어를 통해 Vuex 라이브러리를 설치할 수 있다.

 

npm install vuex

 

Vue Router와 마찬가지로 Vue.js 애플리케이션에 Store를 주입해 모든 컴포넌트에서 this.$store와 같이 접근할 수 있게 할 수 있다.

 

import Vuex from 'vuex';

const store = new Vuex.Store({
	state: {
    	count: 0
    },
    mutations: {
    	increment(state) {
        	state.count++;
        }
    }
});

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

 

기본적으로는 위 예제처럼 구성할 수 있지만 모듈화하여 사용하는게 보다 프로젝트를 관리하기 편해진다. 현재는 count 데이터 하나만을 관리하지만 프로젝트를 구성하다보면 수 많은 데이터들에 대해서 상태관리를 해야하고 이를 위한 Mutation, Action 들이 계속해서 늘어나기 때문이다.

 

Vuex의 기본흐름과 개념


Vuex를 사용하지 않고도 props와 emit, 그리고 data를 사용해서 컴포넌트의 상태(데이터)를 사용할 수 있다.

 

하지만 여러 컴포넌트에서 공통으로 사용하는 상태를 위해 props, emit 등을 계속해서 사용하게 되면 애플리케이션의 복잡성이 늘어날 수밖에 없다. 또한 상태의 여러 복사본을 변경 및 동기화하기 위해서 다양한 방법을 사용하다보면 안정성이 매우 떨어지고 유지보수에 어려움을 가져올 수 있다.

 

두 번째 문제는 부모 컴포넌트와 자식 컴포넌트는 props를 통해서 상태를 공유할 수 있짐나 형제 컴포넌트끼리는 이와같은 방법을 사용할 수 없다는 점이다.

 

이러한 문제를 해결하기 위해서 컴포넌트끼리 직접 상태를 주고받는 Event Bus를 구현할 수 있지만, 이는 컴포넌트 간에 데이터 흐름을 파악하기 어렵다는 문제점이 있다.

Vuex는 State, Mutations, Actions, Getters 로 관리되며, 이러한 구성요소들은 store 객체를 통해서 관리할 수 있다. 이러한 4가지 구성요소들은 위 이미지와 같이 간접적으로 영향을 미치며 단방향 데이터 흐름으로 볼 수 있다.

 

그럼 위 구성요소들에 대해서 자세히 살펴보자.

State(상태)


Vuex는 단일 상태 트리(단일 객체)를 사용한다. 즉, 애플리케이션에는 하나의 원본 데이터만을 사용한다는 말이다. 복사본을 사용하는 것이 아니라 어떠한 컴포넌트에서 데이터가 필요할 때 원본 데이터를 꺼내서 사용하게 된다.

 

이러한 단일 상태 트리의 장점은 애플리케이션에서 사용하는 데이터의 흐름을 추적하기 매우 쉽다는 점이 있다. 이는 디버깅하기 쉽다는 장점으로 이어진다.

 

const store = new Vuex.Store({
	state: {
    	count: 0
    }
});

 

이와 같이 상태를 Vuex Store에 추가할 수 있고, Vue 애플리케이션에 이 Store를 주입했다면 모든 컴포넌트에서 아래와 같은 방법으로 해당 상태를 사용할 수 있다.

 

const Counter = {
	template: '<div> {{ count }} </div>',
    computed: {
    	count() {
        	return this.$store.state.count;
        }
    }
}

 

mapState 를 사용하는 방법도 있는데 나중에 필요할 때 추가할 예정이다.

Mutations(변이)


변이는 Vuex Store에 있는 상태 값을 변경하는 유일한 방법이다. 즉, Mutation을 사용하지 않고 상태 값을 변경할 수 있는 방법은 없다. 이러한 변이는 이벤트와 매우 유사한 형태로 생겼다. 변이는 Type과 Handler로 구성되고 Handler의 첫 번째 인자로는 반드시 상태(state)를 받아야 한다.

 

const store = new Vuex.Store({
	state: {
    	count: 0
    },
    mutations: {
    	increment(state) {
        	state.count++;
        },
        decrement(state) {
        	state.count--;
        }
    }
});

 

이와 같이 Mutation(변이)를 추가할 수 있다. 

 

<!-- Counter -->
<template>
	<div id="app">
    	<button @click="$store.commit('increment')">증가</button>
        <button @click="$store.commit('decrement')">감소</button>
        {{ count }}
    </div>
</template>

<script>
	export default {
    	name: 'App',
        computed: {
        	count() {
            	return this.$store.state.count;
            }
        }
    }
</script>

 

추가한 변이는 직접 호출하는 것이 아니라 Vue Store 객체의 commit 메서드를 통해서 호출하게 된다.

 

const store = new Vuex.Store({
	state: {
    	count: 0
    },
    mutations: {
    	plus(state, payload) {
        	state.count += payload.amount;
        },
    }
});
this.$store.commit('plus', {amount: 999});
this.$store.commit({type: 'plus', amount: 999});

 

변이를 호출할 때 위 예제와 같이 payload를 함께 전달해 사용할 수 있다. 두 번째와 같이 객체 스타일로 Commit할 수도 있지만 굳이...? 라는 생각이 들긴한다.

 

Mutation(변이)를 사용함에 있어 참고할만한 팁 몇가지를 소개하려고 한다.

1. Vue 반응성(reactivity) 규칙을 따라야 한다.

Vuex Store에 저장된 상태 값은 Vue에 의해서 반응하기 때문에 상태 값이 변경되면 해당 상태 값을 관찰하는 모든 Vue 컴포넌트들이 자동으로 업데이트 된다.

 

 - 변경을 감지하고자하는 데이터를 미리 초기화한다.

 - 만약 객체에 새로운 속성을 추가하길 원한다면 다음 중 하나의 방법을 통해야 한다.

 

Vue.set(object, key, value) 메소드를 사용해 객체에 새로운 속성을 추가한다.

 

Vue.set(obj, 'newProp', 123);

 

객체를 새로운 객체로 교체한다.

 

this.$store.state.obj = {...state.obj, newProp: 123}

 

위 방법을 사용하지 않고 단순하게 this.obj['newProp'] = 123 코드로 새로운 속성을 추가하게 되면 해당 속성을 Vue의 반응성 규칙을 따르지 않게 된다.

2. Mutation(변이) Type으로 상수 사용을 지향한다.

변이 타입에 상수를 사용하는 것은 일반적인 패턴이고, 아래와 같은 코드를 통해 상수를 사용할 수 있다.

 

// mutation-type.ts
export const SOME_MUTATION = 'SOME_MUTATION';

// store.ts
import Vuex from 'vuex';
import { SOME_MUTATION } from './mutaion.types';

const store = new Vuex.Store({
	state: { ... },
    mutations: {
    	[SOME_MUTATION](state) {
        
        }
    }
});

3. Mutation(변이)는 반드시 동기적이여야 한다.

변이 핸들러 함수는 반드시 동기적이여야 한다. 만약 비동기적인 흐름을 포함하고 싶다면 이어서 소개할 Action을 통해 처리해야 한다. 

 

변이는 반드시 동기적이여야 한다는 것은, 변이는 단지 상태를 수정하는 기능만 제공해야 한다는 것을 말한다.

Actions(액션)


액션은 상태 값들을 변이 시키는 것이 아니라 Ajax(Axios)를 통해 데이터를 읽어오거나 다양한 과정을 거친 후 변이를 호출(Commit)해 데이터를 변경시키기 위해 사용된다. 사용자는 변이를 직접 호출해도 되지만 일반적으로 액션을 호출하고, 액션에서 일련의 과정을 마친 후 변이를 호출해 상태 값을 변경하게 된다.

 

여기서 Commit은 말 그대로 변경을 확정짓는 일 이라고 표현할 수 있다. 따라서 기본적인 흐름은 비동기 흐름과 같은 것(서버로부터 데이터를 읽어옴)을 수행한 후 최종적으로 Commit을 호출해 변이 핸들러 함수를 호출해 상태를 변경하는 것이다.

State, Mutation, Action을 사용하는 간단한 예시를 살펴보자.

 

const store = new Vuex.Store({
	state: {
    	isLoading: false,
        posts: [],
    },
    mutations: {
    	CHANGE_LOAD_STATUS(state, status=true) {
        	state.isLoading = status;
        },
        SET_POST_DATA(state, posts) {
        	state.posts = posts;
        }
    },
    actions: {
    	async getUserPostData({ commit }) {
        	try {
            	commit("CHANGE_LOAD_STATUS");
                const { id } = await requestUserData(); // 비동기 흐름(유저데이터 읽어오기)
                const posts = await requestPostData(id); // 비동기 흐름(게시글 정보 읽어오기)
                
                commit("SET_POST_DATA", posts);
            } catch(err) {
            	console.error(err);
            } finally {
            	commit("CHANGE_LOAD_STATUS", false);
            }
        }
    }
});

 

이렇게 구성한 Vuex Store 객체는 다음 코드를 통해 활용할 수 있다.

 

<template>
	<div>
    	<p v-if="isLoading"> Loading.... </p>
        <ul v-else>
        	<li v-for="post in posts" :key="post.id"> {{ post.title }} </li>
        </ul>
    <div>
</template>

<script lang="ts">
	export default {
    	mounted() {
        	this.$store.dispatch("getUserPostData");
        },
        computed() {
        	isLoading() {
            	return this.$store.state.isLoading;
            },
            posts() {
            	return this.$store.state.posts;
            }
        }
    }
</script>

위 예제에서 computed를 통해서 데이터를 생성하는 패턴은 잘 기억해두는게 좋을 듯 하다. 나도 실제 프로젝트할 때 사용했던 패턴이고 이후 이 방식과 mapState를 사용한 방식을 비교하기 위해서다.

(아, 그리고 lifecycle 핸들러 함수들과 computed, data등이 선언되는 타이밍에 대해서도 찾아봐야겠다...)

 

이렇게 Vuex를 사용함으로써 UI를 담당하는 부분(Presentation Component)와 데이터를 처리하는 부분(Container Component)를 완전히 분리되어지기 때문에 유지보수가 쉬워진다는 장점이 있다.

Getters(게터)


Getter는 컴포넌트의 computed와 비슷한 역할을 수행한다. 즉, Vuex Store로부터 상태 값을 읽어올 때 바로 읽어오는 것이 아니라 해당 상태 값을 활용해 계산된 속성으로 읽어오게 된다. 예를 들면, 특정 배열 상태 값이 있다고 할 때 해당 상태 값의 길이를 읽어오는 Getter를 생성해 사용할 수 있다.

 

<script lang="ts">
	export default {
    	computed: {
        	posts_count() {
            	return this.$state.store.posts.length;
            }
        }
    }
</script>

 

위 코드처럼 UI 컴포넌트에서 읽어온 데이터(상태)에 대해서 계산하는 것이 아니라 Vuex Store쪽에서 계산해서 반환해주도록 하자! 라는게 Getter의 필요성이고 역할이라고 할 수 있다.

 

getters: {
	postCount(state) {
    	return state.posts.length;
    }
}
<script lang="ts">
	export default {
    	computed: {
        	posts_count() {
            	return this.$state.getters.postCount;
            }
        }
    }
</script>

 

이 처럼 Getter를 사용함으로써 데이터에 대한 모든 처리를 Vuex Store에 집중시킬 수 있다.

 

또한 Getter에 인자를 추가해 호출할 수도 있는데 아래 예시와 같이 특정 Index(id)를 가지는 정보를 추출하기 위해 사용될 수 있다. (자주 사용되는 패턴인듯!?)

 

getters: {
	doneTodoById: (state) => (id) => {
    	return state.todos.find(todo => todo.id === id);
    }
}

// 호출하는 부분
this.$store.getters.getTodoById(2);

 

반응형

'Front-End > Vue.js' 카테고리의 다른 글

[Vue.js] Vue Router 라이브러리  (0) 2021.05.06
[Vue.js] 환경 구성 및 프로젝트 구조  (0) 2021.04.08
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함