상태 관리 (State Management)

상태(state)란 현재에 대한 정보(data)이다.

Web application에서의 상태는 현재 app이 가지고 있는 data로 표현할 수 있다.

여러개의 component를 조합해서 하나의 app을 만들고 있는데 각 component는 독립적이기 때문에 각각의 상태 (data)를 가진다. 하지만 결국 하나의 app을 구성해야하기 때문에 여러개의 component가 같은 상태(data)를 유지할 필요가 있어서 상태관리가 필요하다.

 

pass props & emit event

props와 event를 이용해서 상태관리를 했다. 같은 데이터를 공유하고 있으므로 각 컴포넌트가 동일한 상태를 유지하고 있다. 그러나 component의 중첩이 깊어지면 데이터 전달이 쉽지 않다. 공통의 상태를 유지해야하는 component가 많아지면 데이터 전달 구조가 복잡해진다.

 

A에서 B로 데이터를 전달하기 위해서 단계적으로 처리해야한다. emit event 3번, pass props 3번이 총 6단계가 필요하다.

 

 

 

Centralized Store

중앙 저장소(store)에 데이터를 모아서 상태 관리

각 component는 중앙 저장소의 데이터를 사용한다.

component의 계층에 관계없이 중앙 저장소에 접근해서 데이터를 얻거나 변경할 수 있다. 중앙 저장소의 데이터가 변경되면 각 component는 해당 데이터의 변화에 반응하여 새로 변경된 데이터를 반영한다.

 

 

 

Vuex

state management pattern + library for vue.js (상태 관리 패턴 + 라이브러리)

중앙저장소를 통해 상태관리를 할 수 있도록 하는 라이브러리

데이터가 예측 가능한 방식으로만 변경 될 수 있도록 하는 규칙을 설정하고 vue의 반응성을 효율적으로 사용하는 상태관리 기능을 제공한다.

 

Vuex는 공유된 상태 관리를 처리하는데 유용하지만, 애플리케이션이 단순하다면 Vuex가 없는 것이 더 효율적일 수 있다.

중대형 규모의 SPA를 구축하는 경우에는 Vuex를 이용하는 것이 좋고 적절한 상황에서 Vuex 라이브러리 효용을 극대화할 수 있다. 필요한 순간에 사용하도록 한다.

 

 

 

vue create vuex-app // vue 프로젝트 생성
cd vuex-app //디렉토리 이동
vue add vuex //vue cli를 통해 vuex plugin 적용

vue 프로젝트를 생성하고 vuex plugin을 적용했다.

 

src에 store라는 디렉토리가 생겼다. (중앙저장소)

 

 

 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

store의 index.js에 기본으로 위와 같은 코드가 있다.

 

state는 vue instance의 data이고,

getters는 computed이다.

mutations, actions는 methods가 메서드의 역할에 따라 나누어진 것이다.

 

 

 

 

1. state

vue 인스턴스의 data에 해당

중앙에서 관리하는 모든 상태 정보

개별 component는 state에서 데이터를 가져와서 사용

개별 component가 관리하던 data를 중앙저장소(vuex store의 state)에서 관리하게 된다.

state의 데이터가 변화하면 해당 데이터를 사용(공유)하는 component도 자동으로 다시 렌더링 된다.

$store.state로 state 데이터에 접근 한다.

 

 

 

2. mutations

실제로 state를 변경하는 유일한 방법

 vue 인스턴스의 methods에 해당하지만 mutations에서 호출되는 handler함수는 반드시 동기적이어야 한다. 

비동기 로직으로 mutations를 사용해서 state를 변경하는 경우 state 변화시기를 특정할 수 없기 때문이다.

첫번째 인자로 state를 받으며 component 혹은 actions에서 commit() 메서드로 호출된다. (mutation, action에서 호출되는 함수를 handler 함수라고 한다.)

 

그냥 mutations 만으로 state를 변경하는 것은 가능하지만,

저장소의 각 컨셉(state, getters, mutations, actions)은 각자의 역할이 존재하도록 설계 되었다.

 

 

 

3. actions

mutations와 비슷하지만 비동기 작업을 포함할 수 있다는 차이가 있다.

state를 직접 변경하지 않고 commit()메서드로 mutations를 호출해서 state를 변경한다.

context 객체를 인자로 받고 이 객체를 통해 store.js의 모든 요소와 메서드에 접근할 수 있다.

(state를 직접변경할 수 있지만 하지 않아야 한다.)

component에서 dispatch() 메서드에 의해 호출된다.

 

 

 

vue component의 methods 역할이 vuex에서는 분화된다.

mutations : state 변경

actions : state 변경을 제외한 나머지 로직

 

 

 

4. getter

vue 인스턴스의 computed에 해당

state를 활용하여 계산된 값을 얻고자 할 때 사용한다. state의 원본 데이터를 건들지 않고 계산된 값을 얻을 수 있다.

computed와 마찬가지로 getters의 결과는 캐시(cache) 되며, 종속된 값이 변경된 경우에만 재계산된다.

getters에서 계산된 값은 state 원본에 영향을 미치지 않는다.

첫번째 인자는 state, 두번째 인자는 getter를 받는다.

 

 

 

 

 

 

state

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    message : 'message in store'
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})

store - index.js

store 의 state에 message라는 변수와 메세지 데이터를 추가했다.

 

<template>
  <div id="app">
    <h1>{{ $store.state.message }} </h1>
  </div>
</template>

App.vue

$store.state.으로 message에 접근했다.

 

화면에서 확인했다.

 

 

<template>
  <div id="app">
    <h1>{{ message }} </h1>
  </div>
</template>

<script>

export default {
  name: 'App',
  components: {
  },
  computed : {
    message(){
      return this.$store.state.message
    }
  }
}
</script>

App.vue

위와 같이 $store.state로 바로 접근하는 것 보다 computed에 정의 후 접근하는 것을 권장한다.

 

 

 

 

actions

state를 변경할 수 있는 mutations 호출한다. component에서 dispatch()에 의해 호출된다.

 

<template>
  <div id="app">
    <h1>{{ message }} </h1>
    <input type="text" @keyup.enter = "changeMessage" v-model="inputData">
  </div>
</template>

<script>

export default {
  name: 'App',
  data(){
    return {
      inputData : null
    }
  },
  computed : {
    message(){
      return this.$store.state.message
    }
  },
  methods : {
    changeMessage(){
      const newMessage = this.inputData
      this.$store.dispatch('changeMessage',newMessage)
      this.inputData = null
    } 
  
  }
}
</script>

actions에 정의된 changeMessage 함수에 데이터를 전달한다.

component에서 actions는 dispatch()에 의해 호출된다.

dispatch(호출하고자 하는 actions함수, 넘겨주는데이터)

 

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    message : 'message in store'
  },
  getters: {
  },
  mutations: {
  },
  actions: {
    changeMessage(context, message){
      console.log(context)
      console.log(message)
    }
  },
  modules: {
  }
})

store - index.js

 

actions에 changeMessage를 정의했다.

첫번째 인자는 context이고, 두번째 인자는 넘겨준 데이터(payload)를 받아서 사용한다.

context는 store의 전반적인 속성을 모두 가지고 있으므로 context.state와 context.getters를 통해 mutations를 호출하는 것이 모두 가능하다.

dispatch()를 사용해서 다른 actions도 호출할 수 있다.

단, actions에서 state를 직접 조작하지 않아야 한다.

 

context와 message를 console에서 확인할 수 있다.

context에 state가 있어서 접근가능하지만, 하지 않도록 한다.

 

 

 

 

mutations

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    message : 'message in store'
  },
  getters: {
  },
  mutations: {
    CHANGE_MESSAGE(state,message){
      state.message = message
    }
  },
  actions: {
    changeMessage(context, message){
      context.commit('CHANGE_MESSAGE',message)
    }
  },
  modules: {
  }
})

component 혹은 actions에서 commit()을 통해 mutations를 호출한다.

state를 변경하기 위해서는 mutations 호출해야한다.

mutations는 state를 변경하는 유일한 방법이다.

mutations 함수의 첫번째 인자는 state, 두번쨰 인자는 payload이다.

mutations 이름은 구분을 위해 대문자를 사용하도록 권장된다.

 

state.message는 state에 작성된 message를 지정한다.

payload 가져온 값을 state.message에 할당했다.

 

 

 

hello를 입력하고 enter치면,  state.message이 입력한 값으로 변경된다.

 

 

 

 

getters

  getters: {
    messageLength(state){
      return state.message.length
    },
    doubleLength(state,getters){
      return getters.messageLength*2
    }
  },

getters는 state를 활용한 새로운 변수를 만드는 것이다.

첫번째 인자는 state, 두번째 인자는 getters이다. 두번째 인자를 사용하면 다른 getters의 값을 사용할 수도 있다.

 

 

<script>

export default {
  name: 'App',
  data(){
    return {
      inputData : null
    }
  },
  computed : {
    message(){
      return this.$store.state.message
    },
    messageLength(){
      return this.$store.getters.messageLength
    },
    doubleLength(){
      return this.$store.getters.doubleLength
    }
  },
  methods : {
    changeMessage(){
      const newMessage = this.inputData
      this.$store.dispatch('changeMessage',newMessage)
      this.inputData = null
    } 
  
  }
}
</script>
<template>
  <div id="app">
    <h1>길이 {{messageLength}}의 메세지 {{ message }}를 입력받음</h1>
    <h3>길이x2 : {{doubleLength}}</h3>
    <input type="text" @keyup.enter = "changeMessage" v-model="inputData">
  </div>
</template>

App.vue

getters 역시 state와 마찬가지로 computed에 정의해서 사용하도록 한다.