[Vue] Vuex

wo_ogie·2021년 8월 14일
6
post-thumbnail

Vuex 공식문서
Vuex 공식문서(KR)

Vuex란?


  • Vuex는 Vue.js 애플리케이션 상태 관리 패턴 + 라이브러리
  • 애플리케이션에서 사용하는 모든 데이터를 중앙에서 관리하여, 규모가 크고 복잡한 애플리케이션의 컴포넌트들을 효율적으로 관리할 수 있다

Vuex 등록


// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export const store = new Vuex.Store({
  
});
// main.js
import { store } from './store/store.js';

new Vue({
  ...,
  store,
  ...
});

Vuex 컨셉


  • State : 컴포넌트 간에 공유하는 데이터 data()
  • View : 데이터를 표시하는 화면 template
  • Action : 사용자의 입력에 따라 데이터를 변경하는 methods

    단방향 데이터 흐름 처리를 단순하게 도식화한 그림



Vuex 구조


컴포넌트 -> 비동기 로직 -> 동기 로직 -> 상태

단방향이라는 것이 중요!!



Vuex 기술 요소


  • state : 여러 컴포넌트에 공유되는 데이터 data
  • getters : 연산된 state 값을 접근하는 속성 computed
  • mutations : state 값을 변경하는 이벤트 로직·메서드 methods
  • actions : 비동기 처리 로직을 선언하는 메서드 async methods

1. state

  • 여러 컴포넌트 간에 공유할 데이터 - 상태(state)
// Vue
data: {
  message: 'Hello Vue.js!'
}

// Vuex
state: {
  message: 'Hello Vue.js!'
}
<!-- Vue -->
<p>{{ message }}</p>

<!-- Vuex -->
<p>{{ this.$store.state.message }}</p>

2. getters

  • state 값을 접근하는 속성이자 computed()처럼 미리 연산된 값을 접근하는 속성
// store.js
state: {
  num: 10
},
getters: {
  getNum(state) {
    return state.num;
  },
  doubleNum(state) {
    return state.num * 2;
  }
}
<p>{{ this.$store.getters.getNum }}</p>
<p>{{ this.$store.getters.doubleNum }}</p>

3. mutations

  • state의 값을 변경할 수 있는 유일한 방법이자 메서드(methods)
  • mutations는 commit()으로 동작시킨다
// store.js
state: { num: 10 },
mutations: {
  printNum(state) {
    return state.num;
  },
  sumNum(state, anotherNum) {
    return state.num + anotherNum;
  }
}

// App.vue
this.$store.commit('printNum');
this.$store.commit('sumNum', 20);

mutations의 commit() 형식

  • state를 변경하기 위해 mutations를 동작시킬 때 인자(payload)를 전달할 수 있음. payload의 이름은 자유롭게 설정
// store.js
state: { storeNum: 10 },
mutations: {
  modifyState(state, payload) {
    console.log(payload.str);
    return state.storeNum += payload.num;
  }
}

// App.vue
this.$store.commit('modifyState', {
  str: 'passed from payload!!',
  num: 20
});

왜 state는 직접 변경하지 않고, mutations로 변경할까?

  • 여러 개의 컴포넌트에서 아래와 같이 state 값을 변경하는 경우 어느 컴포넌트에서 해당 state를 변경했는지 추적하기가 어렵다
methods: {
  increaseCounter() { this.$store.state.counter++; }
}
  • 특정 시점에 어떤 컴포넌트가 state를 접근하여 변경한 건지 확인하기 어렵기 때문

  • 따라서, 뷰의 반응성을 거스르지 않게 명시적으로 상태 변화를 수행.
    반응성, 디버깅, 테스팅 혜택


4. actions

  • 비동기 처리 로직을 선언하는 메서드. 비동기 로직을 담당하는 mutations
  • 데이터 요청, Promise, ES6 async와 같은 비동기 처리는 모두 actions에 선언
  • actions는 dispatch()로 동작시킨다

아래는 actions에서 mutations를 사용할 때의 흐름을 확인해 볼 수 있는 코드이다

// store.js
state: {
  num: 10
},
mutations: {
  doubleNum(state) {	// 3️⃣
    return state.num * 2;
  }
},
actions: {
  delayDoubleNum(context) {	// 2️⃣ context로 store의 메서드와 속성에 접근
    context.commit('doubleNum');
  }
}

// App.vue
this.$store.dispatch('delayDoubleNum');	// 1️⃣

actions 비동기 코드 example1

// store.js
mutations: {
  addCounter(state) {
    state.counter++;
  }
},
actions: {
  delayedAddCounter(context) {
    setTimeout(() => context.commit('addCounter'), 2000);
  }
}

// App.vue
methods: {
  incrementCounter() {
    this.$store.dispatch('delayedAddCounter');
  }
}

actions 비동기 코드 example2

// store.js
mutations: {
  setData(state, fetchedData) {
    state.product = fetched.Data;
  }
},
actions: {
  fetchProductData(context) {
    return axios.get('https://domain.com/products/1')
      		.then(response => context.commit('setData', response));
  }
}

// App.vue
methods: {
  getProduct() {
    this.$store.dispatch('fetchProductData');
  }
}

왜 비동기 처리 로직은 actions에 선언해야 할까?

  • 언제, 어느 컴포넌트에서 해당 state를 호출하고, 변경했는지 확인하기가 어려움
  • state 값의 변화를 추적하기가 어렵기 때문에 mutations 속성에는 동기 처리 로직만, actions 속성에는 비동기 처리 로직만 넣어야 한다.

[그림] 여러 개의 컴포넌트에서 mutations로 시간 차를 두고 state를 변경하는 경우


5.modules

store 속성 모듈화

// store.js
export const store = new Vuex.store({
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
});

위 코드를 모듈화 하면 아래처럼 작성할 수 있다

/// store.js
import * as getters from 'store/getters.js';
import * as mutations from 'store/mutations.js';
import * as actions from 'store/actions.js';

export const store = new Vuex.store({
  state: { ... },
  getters,	// getters: getters,
  mutations,	// mutations: mutations
  actions	// actions: actions
});

store 모듈화

  • 앱 규모가 커서 1개의 store로는 관리가 힘들 때 modules 속성 사용
import Vue from 'vue';
import Vuex from 'vuex';
import todo from 'modules/todo.js';

export const store = new Vuex.Store({
  modules: {
    moduleA: todo,	// 모듈 명칭 : 모듈 파일 이름
  }
});

// todo.js
const state = { ... };
const getters = { ... };
const mutations = { ... };
const actions = { ... };



Helper


0. Helper 사용법

  • Helper를 사용하고자 하는 vue 파일에서 아래와 같이 해당 Helper를 loading
// App.vue
import { mapState } from 'vuex';
import { mapGetters } from 'vuex';
import { mapMutations } from 'vuex';
import { mapActions } from 'vuex';

export default {
  computed: { ...mapState(['num']), ...mapGetters(['countedNum']) },
  methods: { ...mapMutations(['clickBtn']), ...mapActions(['asyncClickBtn']) }
};

1. mapState

  • Vuex에 선언한 state 속성을 뷰 컴포넌트에 더 쉽게 연결하는 Helper
// store.js
state: {
  num: 10
}

// App.vue
import { mapState } from 'vuex';

computed: {
  ...mapState(['num'])
}
<!-- <p>{{ this.$store.state.num }}</p> -->
<p>{{ this.num }}</p>

2. mapGetters

  • Vuex에 선언한 getters 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 Helper
// store.js
getters: {
  reverseMessage(state) {
    return state.msg.split('').reverse().join('');
  }
}

// App.vue
import { mapGetters } from 'vuex';

computed: { ...mapGetters(['reverseMessage']) }
<!-- <p>{{ this.$store.getters.reverseMessage }}</p> -->
<p>{{ this.reverseMessage }}</p>

3. mapMutations

  • Vuex에 선언한 mutations 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 Helper
  • 인자를 작성하지 않아도 호출할 때 인자를 암묵적으로 넘겨준다

Example1

// store.js
mutations: {
  clickBtn(state) {
    alert(state.msg);
  }
}

// App.vue
import { mapMutations } from 'vuex';

methods: {
  ...mapMutations(['clickBtn']),
  authLogin() {},
  displayTable() {}
}
<button @click="clickBtn">popup message</button>

Example2

// store.js
state: { storeNum: 10 },
mutations: {
  modifyState(state, payload) {
    console.log(payload.str);
    return state.storeNum += payload.num;
  }
}

// App.vue
import { mapMutations } from 'vuex';

methods: {
  // 인자를 작성하지 않아도 호출할 때 인자가 있다면 암묵적으로 넘겨준다
  ...mapMutations(['modifyState'])
}
<button @click="modifyState({str, num})"></button>

4. mapActions

  • Vuex에 선언한 actions 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 Helper
// store.js
actions: {
  delayClickBtn(context) {
    setTimeout(() => context.commit('clickBtn'), 2000);
  }
}

// App.vue
import { mapActions } from 'vuex';

methods: {
  ...mapActions(['delayClickBtn']),
}
<button @click="delayClickBtn">delay popup message</button>

5. Helper의 유연한 문법

  • Vuex에 선언한 속성을 그대로 컴포넌트에 연결하는 문법
// 배열 리터럴
...mapMutations([
  'clickBtn'
])
  • Vuex에 선언한 속성을 컴포넌트의 특정 메서드에 연결하는 문법
// 객체 리터럴
...mapMutations({
  popupMsg: 'clickBtn'	// 컴포넌트 methods 이름 : Store의 mutations 이름
})

0개의 댓글