개발 일기

Vuex ? 본문

Client/vue

Vuex ?

이건욱

Vuex ?

Vuex는 Vue.js에서 제공하는 상태 관리 패턴 + 라이브러리 입니다.

React에서 Redux처럼 중앙 집중식 저장소 역할을 하며 상태를 변경할 수가 있습니다.

그리고 Vue에 공식 확장 프로그램을 사용해서 디버깅을 제공합니다.

 

상태 관리 패턴 ?

new Vue({
  // 상태
  data () {
    return {
      count: 0
    }
  },
  // 뷰
  template: `
    <div>{{ count }}</div>
  `,
  // 액션
  methods: {
    increment () {
      this.count++
    }
  }
})

먼저 위와 같이 상태 , 뷰 , 액션이 단방향 데이터 흐름에 단순한 도표입니다.

 

이러한 공통된 상태를 공유하는 여러 컴포넌트가 존재했을 때에는 복잡해지기 시작합니다.

 

먼저 여러 뷰가 같은 상태를 의존하기 위해서는 prop가 장황해질수가 있고 이벤트를 통해 넘겨야 하는데

이러한 코드들은 유지보수가 하기가 힘들어 집니다.

 

이러한 방법을 해결하기 위해서 컴포넌트의 공유된 상태를 전역 싱글톤으로 관리를 해서 모든 컴포넌트는 상태에 액세스하거나 동작을 트리거 할수가 있습니다.

 

기존의 Flux , Redux , The Elm Architecture에서 영감을 받아서 Vuex 만들어졌습니다.

설명

모든 Vuex 애플리케이션의 중심에서는 store가 존재합니다.

따라서 이러한 저장소가 애플리케이션에 상태를 보관하고 관리합니다.

일반적인 전역 객체와는 다른 이유는 Vuex 저장소는 반응형 입니다. 그래서 Vue 컴포넌트가 상태가 변경되면 효율적으로 대응하고 업데이트 됩니다.

 

이러한 추가적인 작업을 해주기 때문에 Vuex에서는 저장소의 상태를 직접 변경을 할수가 없습니다.

유일한 방법이 커밋을 통해서 변경을 하기 때문에 모든 상태에 대한 기록이 남아서 디버그를 진행할수가 있습니다.

 

설치

NPM

npm install vuex --save

그리고 지원하는 브라우저가 Promise을 지원하지 않는다면 es6-promise 같은 polyfill 라이브러리를 사용해서 추가를 진행해야합니다.

//cdn
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>

//npm
npm install es6-promise --save # NPM

먼저 간단한 예시를 아래에서 다 보여드리고 추가적인 설명을 진행하겠습니다.

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

Vue.use(Vuex)

export default new Vuex.Store({
  // 상태를 보관합니다.
  state: {
    count: 0
  },
  // 실제로 상태를 변경하는 함수를 의미합니다.
  mutations: {
    increment (state) {
      state.count++
    },
    decrement (state) {
      state.count--
    }
  }
})
/* main.js */

import Vue from 'vue'
import App from './App.vue'
import store from './store';

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  // store 옵션을 사용하여 저장소를 제공합니다.
  // 모든 하위 컴포넌트에서 저장소 인스턴스가 제공합니다.
  store: store
}).$mount('#app')

/* component */
<template>
  <div class="hello">
  	// 저장소에 데이터를 가져옵니다.
    <b>count : {{this.$store.state.count}}</b><br>
    <input type="button" @click="increment()" value="increment"/>
    <input type="button" @click="decrement()" value="decrement"/>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  methods: {
    // commit을 이용해서 동기적으로 하나씩 증가합니다.
    increment: function () {
      this.$store.commit('increment')
    },
    // commit을 이용해서 동기적으로 하나씩 감사합니다.
    decrement: function () {
      this.$store.commit('decrement')
    }
  }
}
</script>

<style scoped>
</style>

상태

mapState 

기본적으로 위와 같이 상태를 {{this.$store.state}} 이런식으로 통해서 가져올수가 있습니다.

하지만 이러한 속성을 모두 선언하면 반복적이면서 장황해 집니다. 그래서 mapState 헬퍼를 사용해서 키 입력을 줄일수가 있습니다.

<template>
  <div class="hello">
    <b>count : {{count}}</b><br>
    <b>count : {{countPlusLocalState}}</b><br>
    <b>count : {{t1}}</b><br>

    <input type="button" @click="increment()" value="increment"/>
    <input type="button" @click="decrement()" value="decrement"/>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'HelloWorld',
  data () {
    return {

    }
  },
  created: function () {
    console.log(this.$store)
  },
  computed : {
    ...mapState({
    // 화살표 함수는 코드를 매우 간결하게 만들어 줍니다!
    t1: state => state.count /** 'count' */,
    // `this`를 사용하여 로컬 상태에 액세스하려면 일반적인 함수를 사용해야합니다
    countPlusLocalState (state) {
      return state.count + state.count
    }}),
    ...mapState(['count'])
  },
  methods: {
    increment: function () {
      this.$store.commit('increment')
    },
    decrement: function () {
      this.$store.commit('decrement')
    }
  }
}
</script>

<style scoped>
</style>

위에 다양한 케이스에 대해서 적어놓았습니다.

이름을 t1 같이 변경을 할수도 있고 , 함수를 이용해서 보여줄 때 특정한 값을 추가할수도 있고, 간단하게는 count을 통해서 얻어올수도 있습니다.

 

Getters 

Vuex에서는 Getters 정의 할수가 있습니다. 

그래서 Getter의 결과는 종속성에 따라 캐쉬되고, 해당 종속성이 변경이 될 경우에만 재 계산을 합니다.

 

아래와 같이 접근이 가능합니다.

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    square : state => {
        return state.count * state.count
    } 
  }
})
<template>
  <div class="hello">
    <b>count : {{this.$store.getters.square}}</b><br>
    <input type="button" @click="increment()" value="increment"/>
    <input type="button" @click="decrement()" value="decrement"/>
  </div>
</template>

하지만 위와 같이 this.$store.getters 이렇게 사용하는 방법은 가독성이 좋지 않기 때문에 보통은 헬퍼를 사용합니다.

mapGetters 

mapGetter 헬퍼는 저장소 getter를 로컬 계산된 속성에 매핑합니다.

<template>
  <div class="hello">
    <b>count : {{square}}</b><br>
    <input type="button" @click="increment()" value="increment"/>
    <input type="button" @click="decrement()" value="decrement"/>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'HelloWorld',
  computed : {
    ...mapGetters([
      'square',
    ])
    /**
    ...mapGetters({
      원하시는 이름 : 'square'  이런식으로 별칭이 가능합니다.
    })
    */
  },
  methods: {
    increment: function () {
      this.$store.commit('increment')
    },
    decrement: function () {
      this.$store.commit('decrement')
    }
  }
}
</script>

<style scoped>
</style>

변이

맨 위에 store.commit('decrement') 이런식으로 작성을 통해서 호출을 했지만 특정 페이로드를 가지고 커밋을 하기 위해서는 아래와 같이 추가 인자를 넣을수가 있습니다.

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
store.commit('increment', 10)

// ...
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}
store.commit('increment', {
  amount: 10
})

// ...
store.commit({
  type: 'increment',
  amount: 10
})
mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

대부분의 경우에는 객체로 구성을 하고 그 경우가 더 이해하기가 적합합니다.

 

더 나아가 Flux 구현에서 보통 유형을 상수로 사용하는것이 일반적인 패턴입니다.

상수로 사용하게 되면 디버깅 할때 한눈에 파악이 가능합니다.

 

// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'

// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'

const store = new Vuex.Store({
  state: { ... },
  mutations: {
    // ES2015에서 계산 된 프로퍼티 이름 기능을 사용하여
    // 상수를 함수 이름으로 사용할 수 있습니다
    [SOME_MUTATION] (state) {
      // 변이 상태
    }
  }
})

mapMutations

mutation도 마찬가지로 헬퍼가 존재합니다. 아래와 같이 사용이 가능합니다.

import { mapMutations } from 'vuex'

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment' // this.increment()를 this.$store.commit('increment')에 매핑합니다.
    ]),
    ...mapMutations({
      add: 'increment' // this.add()를 this.$store.commit('increment')에 매핑합니다.
    })
  }
}
 
 
 
 

'Client > vue' 카테고리의 다른 글

vuex - module  (0) 2020.09.15
Vuex - action ?  (0) 2020.09.15
Vue-router  (0) 2020.09.15
클래스와 스타일 바인딩  (0) 2020.09.14
템플릿 문법  (0) 2020.09.01
Comments