๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ’ปWEB FrontEnd/ํ”„๋ ˆ์ž„์›Œํฌ Vue

Vuex

 

<Do it! Vue.js ์ž…๋ฌธ>์„ ๊ธฐ๋ณธ์œผ๋กœ ๋ฐฐ์šด ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

 

๋ชฉ์ฐจ

  • 1. ์ƒํƒœ ๊ด€๋ฆฌ

  • 2. Vuex

  • 3. state

  • 4. Getters & Mutations

  • 5. Actions

  • 6. Vuex ํ—ฌํผ ํ•จ์ˆ˜ (mapGetters, mapMutations, mapActions )

 

์ฐธ๊ณ ํ•œ ์‚ฌ์ดํŠธ

joshua1988.github.io/web-development/vuejs/vuex-start/

joshua1988.github.io/web-development/vuejs/vuex-getters-mutations/

joshua1988.github.io/web-development/vuejs/vuex-actions-modules/

 


1. ์ƒํƒœ ๊ด€๋ฆฌ

1-1) ์ƒํƒœ ๊ด€๋ฆฌ 

์ƒํƒœ ๊ด€๋ฆฌ๋ž€ ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ๊ณผ ์ด๋ฒคํŠธ ํ†ต์‹ ์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ํŒจํ„ด์„ ์˜๋ฏธํ•œ๋‹ค.

ํ”„๋ ˆ์ž„์›Œํฌ ์‚ฌ์šฉํ•˜๋Š” ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
React Redux/ Mobx
Vue Vuex

 

1-2) ์ƒํƒœ ๊ด€๋ฆฌ ํŒจํ„ด

  • ์ƒํƒœ : ์•ฑ์„ ์ž‘๋™ํ•˜๋Š” ์›๋ณธ ์†Œ์Šค
  • ๋ทฐ ์ƒํƒœ์˜ ์„ ์–ธ์  ๋งคํ•‘
  • ์•ก์…˜ : ๋ทฐ์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋Œ€ํ•ด ๋ฐ˜์‘์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๋ฐ”๊พธ๋Š” ๋ฐฉ๋ฒ•

 

 

 

 

new Vue({
  // ์ƒํƒœ
  data () {
    return {
      count: 0
    }
  },
  // ๋ทฐ
  template: `
    <div>{{ count }}</div>
  `,
  // ์•ก์…˜
  methods: {
    increment () {
      this.count++
    }
  }
})

 

1-3) ์ƒํƒœ ๊ด€๋ฆฌ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ 

์•ฑ์˜ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด์„œ ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ์ ๋“ค์€ ์•„๋ž˜ 2๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

  1. ์—ฌ๋Ÿฌ ๋ทฐ๊ฐ€ ๊ฐ™์€ ์ƒํƒœ์— ์˜์กดํ•˜๋Š” ๊ฒฝ์šฐ => ์ง€๋‚˜์น˜๊ฒŒ ์ค‘์ฒฉ๋œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ต๊ณผํ•˜๋Š” prop๋Š” ์žฅํ™ฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ํ˜•์ œ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค. 
  2. ์„œ๋กœ ๋‹ค๋ฅธ ๋ทฐ์˜ ์ž‘์—…์ด ๋™์ผํ•œ ์ƒํƒœ๋ฅผ ๋ฐ˜์˜ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ => ์ง์ ‘ ๋ถ€๋ชจ/์ž์‹ ์ธ์Šคํ„ด์Šค๋ฅผ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด ์ƒํƒœ์˜ ์—ฌ๋Ÿฌ ๋ณต์‚ฌ๋ณธ์„ ๋ณ€๊ฒฝ ๋ฐ ๋™๊ธฐํ™” ํ•˜๋ ค๋Š” ๋“ฑ์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ํŒจํ„ด์€ ๋ชจ๋‘ ๋ถ€์„œ์ง€๊ธฐ ์‰ฝ๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

 


2. Vuex

  • Vue.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ํŒจํ„ด + ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ๋‹ค๋ฅธ ์ƒํƒœ ๊ด€๋ฆฌ ํŒจํ„ด์ด๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋น„๊ตํ–ˆ์„ ๋•Œ ๋ทฐ์˜ ๋ฐ˜์‘์„ฑ(Reactivity) ์ฒด๊ณ„๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค๋Š” ์ฐจ์ด์ ์ด ์žˆ๋‹ค.
  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ค‘์•™ ์ง‘์ค‘์‹ ์ €์žฅ์†Œ ์—ญํ• ์„ ํ•˜๋ฉฐ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋ฐฉ์‹์œผ๋กœ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.

โ€ป ์ค‘๋Œ€ํ˜• ๊ทœ๋ชจ์˜ SPA๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๊ณ , ์•ฑ์ด ๋‹จ์ˆœํ•˜๋‹ค๋ฉด Vuex์—†์ด ๊ฐ„๋‹จํ•œ ๊ธ€๋กœ๋ฒŒ ์ด๋ฒคํŠธ ๋ฒ„์Šค (opens new window)๋งŒ์œผ๋กœ๋„ ์ƒํƒœ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค!

 

2-1) Vuex ์„ค์น˜ ๋ฐ ๋“ฑ๋ก

์•„๋ž˜ ๋ช…๋ น์–ด๋กœ ํ”„๋กœ์ ํŠธ์— vuex๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

$ npm install vuex

๊ทธ๋ฆฌ๊ณ  storeํด๋”์•ˆ์— index.jsํŒŒ์ผ์„ ์ƒ์„ฑํ•ด vuex๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
  // ...
});

๋งˆ์ง€๋ง‰์œผ๋กœ ํ”„๋กœ์ ํŠธ์˜ main.jsํŒŒ์ผ์— store>index.js ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์™€ ๋“ฑ๋กํ•œ๋‹ค.

import store from './store'   //store.js๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ฝ”๋“œ

new Vue({
  el: '#app',
  store,  //์ถ”๊ฐ€!!
  components: { App },
  template: '<App/>'
})

 


3. state

3-1) state ๋“ฑ๋ก

// store.js (Vuex)
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
  // state ์ถ”๊ฐ€
  state: {
    counter: 0
  }
});

 

3-2) state ์ ‘๊ทผ

// App.vue
computed: {
  doubleCounter() {
    return this.$store.state.counter * 2;
  }
},

// Child.vue
computed: {
  doubleCounter() {
    return this.$store.state.counter * 2;
  }
},

 


4. Getters & Mutations

์œ„ ์ฝ”๋“œ๋Š” ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ๊ฐ™์€ ๋กœ์ง์„ ๋น„ํšจ์œจ์ ์œผ๋กœ ์ค‘๋ณต ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ์ด๋ฅผ Vuex์˜ state๋ณ€๊ฒฝ์„ ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ , vuex์—์„œ getters ๋˜๋Š” mutations๋ฅผ ์ด์šฉํ•˜์—ฌ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•˜๊ณ  ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ˆ˜ํ–‰ ๋กœ์ง์„ ํ˜ธ์ถœํ•˜๋ฉด ํšจ์œจ์ ์ด๋‹ค.

๊ณตํ†ต์  getters, mutations, (+actions) ๋ชจ๋‘ state ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง๋“ค์ž„
์ฐจ์ด์  - mutations๋Š” ์ธ์ž๋ฅผ ๋ฐ›์•„ vuex์— ๋„˜๊ฒจ์ค„ ์ˆ˜ ์žˆ๋‹ค.
- getters๋Š” comuted์— ๋“ฑ๋กํ•˜๊ณ , mutations๋Š”methodes์— ๋“ฑ๋กํ•œ๋‹ค.

 

4-1) getters ๋“ฑ๋ก๊ณผ ์‚ฌ์šฉ

// store.js (Vuex)
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    counter: 0
  }
  // getters ์ถ”๊ฐ€
  getters: {
    getCounter: function (state) {
      return state.counter;
    },
    doubleCounter: function (state) {
      return state.counter * 2;
    }
  },
});

 

// App.vue
computed: {
  parentCounter() {
    this.$store.getters.getCounter;
  }
},

// Child.vue
computed: {
  childCounter() {
    this.$store.getters.getCounter;
  }
},

 

4-2) mutations ๋“ฑ๋ก๊ณผ ์‚ฌ์šฉ

// store.js (Vuex)
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    counter: 0
  }
  // mutations ์ถ”๊ฐ€
  mutations: {
    addCounter: function (state, payload) {
      return state.counter++;
    }
  }
});
// App.vue
methods: {
  addCounter() {
    // this.$store.state.counter++;
    this.$store.commit('addCounter');
  }
},

๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ  Vuex ์˜ state ๋ฅผ ์กฐ์ž‘ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ํŠน์ • ๊ฐ’๋“ค์„ ๋„˜๊ธฐ๊ณ  ์‹ถ์„ ๋•Œ๋Š” commit() ์— ๋‘ ๋ฒˆ์งธ ์ธ์ž๋ฅผ ์ถ”๊ฐ€ํ•ด ๊ฐ’์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค.

this.$store.commit('addCounter', 10);
this.$store.commit('addCounter', {
  value: 10,
  arr: ["a", "b", "c"]
});
mutations: {
  // payload ๊ฐ€ { value : 10 } ์ผ ๊ฒฝ์šฐ
  addCounter: function (state, payload) {
    state.counter = payload.value;
  }
}

 


5. Actions

  • Mutations ์—๋Š” ์ˆœ์ฐจ์ ์ธ ๋กœ์ง๋“ค๋งŒ ์„ ์–ธํ•˜๊ณ  Actions ์—๋Š” ๋น„ ์ˆœ์ฐจ์  ๋˜๋Š” ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋กœ์ง๋“ค์„ ์„ ์–ธํ•œ๋‹ค.
  • setTimeout() ์ด๋‚˜ ์„œ๋ฒ„์™€์˜ http ํ†ต์‹  ์ฒ˜๋ฆฌ ๊ฐ™์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ฌ ํƒ€์ด๋ฐ์ด ์˜ˆ์ธก๋˜์ง€ ์•Š์€ ๋กœ์ง์€ Actions ์— ์„ ์–ธํ•œ๋‹ค.

 

5-1) actions ๋“ฑ๋ก

// store.js
export const store = new Vuex.Store({
  actions: {
    getServerData: function (context) {
      return axios.get("sample.json").then(function() {
        // ...
      });
    },
    delayFewMinutes: function (context) {
      return setTimeout(function () {
        commit('addCounter');
      }, 1000);
    }
  }
});

 

5-2) actions ์‚ฌ์šฉ

actions ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด dispatch() ๋ฅผ ์ด์šฉํ•œ๋‹ค.

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

 

5-3) actions์— ์ธ์ž ๊ฐ’ ๋„˜๊ธฐ๊ธฐ

<!-- by ์™€ duration ๋“ฑ์˜ ์—ฌ๋Ÿฌ ์ธ์ž ๊ฐ’์„ ๋„˜๊ธธ ๊ฒฝ์šฐ, ๊ฐ์ฒด์•ˆ์— key - value ํ˜•ํƒœ๋กœ ์—ฌ๋Ÿฌ ๊ฐ’์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค -->
<button @click="asyncIncrement({ by: 50, duration: 500 })">Increment</button>
export const store = new Vuex.Store({
  actions: {
    // payload ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ธ์ž ๋ช…
    asyncIncrement: function (context, payload) {
      return setTimeout(function () {
        context.commit('increment', payload.by);
      }, payload.duration);
    }
  }
})

 


6. Map ํ—ฌํผ

์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…๋œ ์‚ฌ์ดํŠธ

Vuex ์— ๋‚ด์žฅ๋œ helper ํ•จ์ˆ˜๋กœ ์ด๋ฏธ ์œ„์—์„œ ํ•œ๋ฒˆ ๊ฐ€๋…์„ฑ์ด ์˜ฌ๋ผ๊ฐ„ ์ฝ”๋“œ๋ฅผ ๋” ์ง๊ด€์ ์ด๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

  • mapState
  • mapGetters
  • mapMutations
  • mapActions
// ์‚ฌ์šฉ ๋ฐฉ๋ฒ• 1
...mapState(['books', 'cart'])

// ์‚ฌ์šฉ ๋ฐฉ๋ฒ• 2
...mapMutations({
  changeBooks: 'setBooks'
})

// ์‚ฌ์šฉ ๋ฐฉ๋ฒ• 3
...mapGetters({
  CartItemsCount: state => state.cart.length
})

ex)

// App.vue
import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['books']),
    ...mapGetters(['checkOutOfStock']),
  },
  methods: {
    ...mapActions(['fetchBooks', 'addBookToCart']),
    ...mapMutations({changeBooks: 'setBooks'})
  },
}