Post

[Vue3] Flux 패턴을 기반한 상태관리 패턴 Vuex와 헬퍼함수

디자인 패턴, MVC패턴과 FLUX패턴

🐀 디자인패턴이란 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 설계상의 검증된 솔루션을 말한다.

 

📜 MVC 패턴
사용자가 어떤 이벤트를 발생시키면 ControllerModel을 통해서 데이터를 가져오고,
가져온 데이터를 바탕으로 View가 시각적인 표현을 담당하는 세가지 역할로 구분한 패턴이다.
결국, M(상태관리) V(사용자 인터페이스 표시) C(제어 및 전달)로 역할을 나누어
각자의 역할에만 집중할 수 있도록 만든 것이다.

🍕 복잡한 화면을 구성하는 경우, Controller에 다수의 View와 Model이 얽혀진다.

Alt text

하나의 View에 (상태를 관리하는)다수의 Model이 관여할 수 있다.
반대로 하나의 Model은 다수의 View의 상태를 관리할 수 있다.
다수의 방향으로 통신이 일어나고,
Model과 View가 얽혀있기 때문에 에러가 생겼을 때, 이를 추적하기 어렵다.

 

📜 Flux패턴
Flux패턴은 단방향으로만 흐른다. 사용자가 어떤 이벤트를 발생시키면 Action(상태변경 트리거)을 생성한다.
생성된 Action을 Dispatcher에 전달하여 Store(중앙 집중식 상태 저장소)의 상태를 변경한다.
Store의 상태가 변경되면 이를 감지하여 View가 업테이트 된다.

🍕 모든 데이터 흐름을 단방향으로 정형화시켜 에러를 추적하기 쉽다.

Alt text

Vuex

🐁 Vuex는 Flux패턴에서 기인한 상태관리 패턴이자 상태관리 라이브러리이다.

하위 컴포넌트가 많아지고 깊어질수록, (즉 거쳐야될 컴포넌트가 많을 때)
이벤트 발생이나 상태 전달이 번거로워진다.
따라서, 전반적인 컴포넌트에서 사용할 상태를 한군데로 몰고, 여기서 상태 조작이 일어난다.

🎯 Vuex를 쓰는 이유

  • MVC패턴에서 발생하는 구조적 오류 해결
  • 상태 전달이 명시적
  • 다수의 컴포넌트에서 같은 상태를 업데이트할 때, 동기화 문제 해결

 

🐙 Vuex의 구조 및 주요 속성

Alt text

속성설명
state컴포넌트 간에 공유하는 상태
getters연산된 state에 접근하는 일종의 computed
actions비동기 처리 로직을 선언하는 비동기 ver. mutations
mutations동기 처리 로직을 선언하는 메서드
🍪 state를 mutations을 통해 변경하는 이유

: 다수의 컴포넌트에서 state를 공유하고 있기 때문에,
각각의 컴포넌트에서 state를 직접 변경하는 경우 처리 시점을 파악하기 어렵다.
*처리시점이 예상 가능해야 추적이 용이하다
*Vuex의 mutations를 거쳐야 반응성을 얻기 쉽다

🍍 반응성
화면에 특정 데이터가 변경되었을 때, 스크립트에서 바로 인지하는 것
state가 변경되면 Vue Reactivity에 의해 화면이 다시 갱신된다.

+ 비동기 처리 로직을 actions에 선언하는 이유
비동기 로직이 다 처리된 후에, mutations의 method를 호출해 값을 처리하는 프로세스를 규격화한 것이다.

 

🍟 Vuex 사용법
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 내장)

🐁 Vuex의 각 속성들을 더 쉽게 사용하는 방법 mapState / mapGetters / mapMutations / mapActions

⚠️ 헬퍼함수들은 인자를 선언하지 않아도 호출하는 단에서 인자가 있으면 암묵적으로 넘겨준다.
   (단, 인자가 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'})
This post is licensed under CC BY 4.0 by the author.