[Vue3] Flux 패턴을 기반한 상태관리 패턴 Vuex와 헬퍼함수
디자인 패턴, MVC패턴과 FLUX패턴
📜 MVC 패턴
사용자가 어떤 이벤트를 발생시키면Controller는Model을 통해서 데이터를 가져오고,
가져온 데이터를 바탕으로View가 시각적인 표현을 담당하는 세가지 역할로 구분한 패턴이다.
결국,M(상태관리)V(사용자 인터페이스 표시)C(제어 및 전달)로 역할을 나누어
각자의 역할에만 집중할 수 있도록 만든 것이다.
반대로 하나의 Model은 다수의 View의 상태를 관리할 수 있다.
다수의 방향으로 통신이 일어나고,
Model과 View가 얽혀있기 때문에 에러가 생겼을 때, 이를 추적하기 어렵다.
📜 Flux패턴
Flux패턴은단방향으로만 흐른다. 사용자가 어떤 이벤트를 발생시키면Action(상태변경 트리거)을 생성한다.
생성된 Action을Dispatcher에 전달하여Store(중앙 집중식 상태 저장소)의 상태를 변경한다.
Store의 상태가 변경되면 이를 감지하여View가 업테이트 된다.
Vuex
하위 컴포넌트가 많아지고 깊어질수록, (즉 거쳐야될 컴포넌트가 많을 때)
이벤트 발생이나 상태 전달이 번거로워진다.
따라서, 전반적인 컴포넌트에서 사용할 상태를 한군데로 몰고, 여기서 상태 조작이 일어난다.
🎯 Vuex를 쓰는 이유
- MVC패턴에서 발생하는 구조적 오류 해결
- 상태 전달이 명시적
- 다수의 컴포넌트에서 같은 상태를 업데이트할 때, 동기화 문제 해결
| 속성 | 설명 |
|---|---|
| state | 컴포넌트 간에 공유하는 상태 |
| getters | 연산된 state에 접근하는 일종의 computed |
| actions | 비동기 처리 로직을 선언하는 비동기 ver. mutations |
| mutations | 동기 처리 로직을 선언하는 메서드 |
: 다수의 컴포넌트에서 state를 공유하고 있기 때문에,
각각의 컴포넌트에서 state를 직접 변경하는 경우 처리 시점을 파악하기 어렵다.
*처리시점이 예상 가능해야 추적이 용이하다
*Vuex의 mutations를 거쳐야 반응성을 얻기 쉽다
🍍 반응성
화면에 특정 데이터가 변경되었을 때, 스크립트에서 바로 인지하는 것
state가 변경되면 Vue Reactivity에 의해 화면이 다시 갱신된다.
+ 비동기 처리 로직을 actions에 선언하는 이유
비동기 로직이 다 처리된 후에, mutations의 method를 호출해 값을 처리하는 프로세스를 규격화한 것이다.
1
npm install vuex --save
📂src > main.js
1
2
3
4
5
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store/store";
createApp(App).use(store).mount("#app");
📂store > store.js
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
import { createStore } from "vuex";
const store = createStore({
state: {
num: 10,
product: {}
},
mutations: {
doubleNum(state) {
// method의 첫번째 인자로 항상 state를 가져온다
return state.num * 2;
},
setProduct(state, fetchedData) {
state.product = fetchedData;
}
},
actions: {
delayedDoubleNum(context) {
// 첫번째 인자로 항상 context(mutations 접근 경로)를 가져온다
setTimeout(() => context.commit("doubleNum"), 2000); // commit API를 호출해서 mutations의 method를 호출한다
},
fetchedProductData(context) {
return axios
.get("https://domain.com/products/1")
.then((response) => context.commit("setProduct", response));
}
}
});
export default store;
App.vue
1
2
3
4
5
6
7
8
9
10
<script>
methods: {
doubleNumAfter2s() {
this.$store.dispatch('delayedDoubleNum'); // dispatch API를 호출해서 actions의 method를 호출한다
},
getProduct() {
this.$store.dispatch('fetchedProductData');
}
}
</script>
헬퍼함수 (Vuex 내장)
⚠️ 헬퍼함수들은 인자를 선언하지 않아도 호출하는 단에서 인자가 있으면 암묵적으로 넘겨준다.
(단, 인자가 2개 이상일 경우 호출하는 단에서 객체로 묶어주자)
App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
import { mapState, mapGetters, mapMutations, mapActions } from "vuex"; // 헬퍼를 로딩
export default {
computed() {
...mapState(['num']),
...mapGetters(['countedNum'])
},
methods: {
...mapMutations(['clickBtn']),
...mapActions(['delayedClickBtn'])
}
};
</script>
🍍
...Object Spread Operator (ES6)
객체 안의 모든 속성을 뿌려서 집어 넣겠다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let woo = { field: "web", language: "java" }; let developer = { nation: "ko", ...woo }; console.log(developer); //{ // field: "web", // language: "java", // nation: "ko" //}
store.js에서 호출할 method명과 컴포넌트에서 호출할 method명이
- 같을 때:
배열리터럴...mapGetters(['countedNum']) - 다를 때:
객체리터럴...mapGetters({'countedNum': 'storedCountedNum'})


