[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'})