라즈베리파이반

라즈베리파이 등 컴퓨터계열 게시판입니다.

제목Vue.js: Vuex2023-01-12 00:54
작성자user icon Level 4

88x31.png


Vuex는 Vue로 작성된 어플리케이션의 상태를 통합적으로 관리할 수 있도록 도와주는 라이브러리입니다.


어플리케이션의 규모가 커지면 컴포넌트가 많아지고, 각 컴포넌트의 상태는 분산됩니다. 컴포넌트가 많아질 수록 각각의 상태에 대한 상호작용이 어려워지는데, Vuex는 분산된 상태를 하나의 저장소에 모아 복잡한 상호작용을 단순화 할 수 있습니다.



1. Flux 패턴


Flux는 기존의 MVC 패턴의 문제점을 해결하기 위해 페이스북에서 고안한 아키텍처입니다.


MVC 패턴의 컨트롤러(Controller)는 모델(Model)의 데이터를 조회하거나 업데이트하는 역할을 하며, 모델(Model)의 변화는 뷰(View)에 반영됩니다. 또한 사용자가 뷰(View)를 통해 데이터를 입력하면, 모델(Model)에 영향을 주면서 데이터를 관리합니다.


MVC 패턴의 데이터 흐름 


MVC 패턴을 사용함으로써 개발자들은 조금 더 확장이 유연하고 유지 보수하기에 용이한 어플리케이션을 작성할 수 있습니다.


그러나 MVC 패턴의 문제 중 하나는 하나의 컨트롤러가 여러 개의 모델이나 뷰를 컨트롤러를 제어할 수 있다는 점입니다. 페이스북과 같은 대규모 어플리케이션들이 등장하게 되면서 어플리케이션의 내부 구조가 복잡하게 되었습니다.


MVC 패턴의 복잡성 

 


이를 해결하고자 페이스북 개발팀은 Flux 아키텍처를 개발했습니다. Flux 패턴의 경우 MVC 패턴과 달리 데이터 흐름이 양방향이 아닌 단방향으로 흐릅니다.


Flux 패턴의 데이터 흐름 

 


Flux 패턴에서 데이터는 언제나 액션 -> 디스패처 -> 스토어 -> 뷰의 순서로 흐르기 때문에 뷰에서 사용자 액션에 의하여 데이터가 업데이트 된다면 액션부터 다시 시작하여 디스패처를 통해 스토어에 있는 데이터를 업데이트한 후 뷰에 반영합니다.


이러한 단방향 데이터의 흐름은 데이터의 상태를 예측하기 쉽게 만들어 디버깅을 용이하게 함으로써 유지보수를 용이하게 합니다.



1) 액션(Action)


스토어(Store)를 변경하기 위해서는 디스패처(Dispatcher)를 통한 업데이트가 이루어져야 하는데, 이때 디스패처를 실행시키는 인수 객체가 액션(Action)입니다.


액션은 타입(type)페이로드(payload)로 이루어진 단순한 객체입니다.


액션(Action)

{

  type: 'INCREASE_NUM',

  payload: { num: 1 }

}


액션의 타입은 개발자가 직정 정의한 상수입니다. 이러한 액션은 액션 생성자(Action Creator)를 통해 생성됩니다.



2) 디스패처(Dispatcher)


디스패처(Dispatcher)는 Flux의 모든 데이터 흐름을 관리하는 역할을 합니다. 액션에 대한 콜백 함수를 제공하며, 액션이 발생하면 디스패처의 콜백 함수를 통해 액션의 메시지를 전달받습니다.



3) 스토어(Store)


스토어(Store)는 어플리케이션의 모든 상태와 로직을 담고 있으며, MVC 패턴의 모델(Model)과 같은 역할을 합니다.


스토어의 상태를 변경하기 위해서는 반드시 액션을 생성한 후 디스패처를 통해 스토어 상태 변경을 요청해야 합니다.


콜백 함수를 통해 스토어 상태가 변경되면 이벤트를 통해 뷰(View)에 새로운 상태를 전달하여 뷰가 스스로 업데이트 하도록 합니다.



4) 뷰(View)


Flux 패턴의 뷰(View)는 MVC 패턴의 뷰와는 다르게 단순히 화면을 렌더링하는 것 이외에도 컨트롤러(Controller) 역할을 하기도 합니다.


최상위 뷰는 스토어의 상태를 가져와 자식 뷰에 분배하는 역할을 하기 때문에 컨트롤러-뷰(Controller-View)라고도 부릅니다.


자식 뷰는 직접 스토어의 데이터를 받아오는 대신 부모 뷰로부터 Props를 통해 데이터를 전달받습니다.



2. Vuex


Vuex는 Flux 아키텍처에 영감을 받아 제작된 상태관리 라이브러리입니다. 많은 부분에서 Flux 아키텍처의 메커니즘을 차용하고 있습니다.


Vuex의 데이터 흐름


Vue에서 Vuex를 사용하려면 Vue.use(Vuex)를 입력하여 Vuex의 사용을 선언해야합니다.


Vuex 사용 선언

import Vue from 'vue'

import Vuex from 'vuex'


Vue.use(Vuex)


하지만 CDN을 통해 Vuex를 import하여 모듈방식으로 개발하는 경우 Vuex를 로드하면 자동으로 Vuex를 사용하도록 설정되기 때문에 생략가능합니다. Vue 2버전의 경우 Vuex 3버전을 로드합니다.


Vuex의 사용을 선언하고 Vue 인스턴스의 옵션으로 store를 넣어주면 해당 컴포넌트에서 스토어를 사용할 수 있습니다.


index.html

<!DOCTYPE html>

<html lang="ko">

<head>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/vuex@3/dist/vuex.js"></script>

  <script defer type="module" src="./main.js"></script>

  <title>Vue 연습</title>

</head>

<body>

  <div id="app"></div>

</body>

</html>


main.js 

import store from "./store.js";


new Vue({

  store,

}).$mount('#app')


store.js 

export default new Vuex.Store();


이제 스토어의 옵션을 알아보겠습니다.



1) 상태(State)


상태(State)는 어플리케이션에서 공통으로 관리할 상태, 즉 모델(Model)을 의미합니다. Vuex는 단일 상태 트리를 사용하며, 이 단일 객체는 원본 소스의 역할을 합니다. 이는 각 어플리케이션마다 하나의 저장소만 갖게 된다는 것을 의미합니다.


컴포넌트에서 스토어의 접근은 $store 프로퍼티를 통해 접근할 수 있으며, 컴포넌트에서 스토어의 상태에 접근하려면 Vue 인스턴스의 computed를 통해 접근합니다.


index.html 

<!DOCTYPE html>

<html lang="ko">

<head>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/vuex@3/dist/vuex.js"></script>

  <script defer type="module" src="./main.js"></script>

  <title>Vue 연습</title>

</head>

<body>

  <div id="app">{{ num }}</div>

</body>

</html>


main.js 

import store from "./store.js";


new Vue({

  store,

  computed: {

    num() {

      return this.$store.state.num;

    }

  }

}).$mount('#app')


store.js 

export default new Vuex.Store({

  state: {

    num: 0

  }

});


출력 결과 

 


this.$store.state를 통해 상태에 접근하면 여러 상태에 접근할 경우 반복적이고 장황해질 수 있는데, 이때 Vuex에서 제공하는 mapState 헬퍼 함수를 사용하면 간결하게 접근할 수 있습니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: Vuex.mapState(['num'])

}).$mount('#app')


헬퍼 함수 뿐만 아니라 다른 computed를 함께 사용하고 싶다면 객체 전개 연산자(Object Spread Operator)를 사용하면 됩니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: {

    ...Vuex.mapState(['num'])

  }

}).$mount('#app')




2) 게터(Getters)


게터(Getters)는 스토어 내에서 Vue의 computed와 같은 역할을 합니다. 컴포넌트에서 스토어 상태를 기반으로 상태를 계산하는 경우 다음과 같이 작성할 수 있습니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: {

    double: {

      return 2 * this.$store.state.num;

    }

  }

}).$mount('#app')



double 데이터는 스토어에 있는 num 데이터에 2를 곱한 값을 반환합니다. 간단한 식이기 때문에 문제없어 보이지만 만약 식이 복잡할 경우 해당 리턴값을 다른 컴포넌트에서 사용한다면 동일한 식을 중복해서 작성해야 합니다.


게터를 사용한다면 중복된 식을 각각의 컴포넌트에 작성할 필요없이 스토어로부터 캐시된 데이터값을 가져올 수 있게 됩니다.


index.html

<!DOCTYPE html>

<html lang="ko">

<head>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/vuex@3/dist/vuex.js"></script>

  <script defer type="module" src="./main.js"></script>

  <title>Vue 연습</title>

</head>

<body>

  <div id="app">{{ double }}</div>

</body>

</html>


main.js 

import store from "./store.js";


new Vue({

  store,

  computed: {

    double() {

      return this.$store.getters.double;

    }

  }

}).$mount('#app')


store.js 

export default new Vuex.Store({

  state: {

    num: 2

  },

  getters: {

    double(state, _getters){

      return 2 * state.num

    }

  }

});


출력 결과 

 


getters에 선언된 함수는 1번째 매개변수로 스토어의 상태를, 2번째 매개변수로 getters 속성 자체를 전달받습니다.


상태와 마찬가지로 Vuex의 mapGetters 헬퍼 함수를 통해 간결한 접근을 할 수도 있습니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: Vuex.mapGetters(['double'])

}).$mount('#app')


역시 헬퍼 함수 뿐만 아니라 다른 computed를 함께 사용하고 싶다면 객체 전개 연산자(Object Spread Operator)를 사용하면 됩니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: {

    ...Vuex.mapGetters(['double'])

  }

}).$mount('#app')



3) 변이(Mutation)


변이(Mutation)는 스토어 상태를 변경할 수 있는 유일한 방법입니다. 변이 함수의 이름은 액션의 타입과 동일하며, 스토어 상태를 변경하는 로직을 가집니다.


index.html

<!DOCTYPE html>

<html lang="ko">

<head>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/vuex@3/dist/vuex.js"></script>

  <script defer type="module" src="./main.js"></script>

  <title>Vue 연습</title>

</head>

<body>

  <div id="app">

    <div>{{ num }}</div>

    <button @click="increment">증가</button>

  </div>

</body>

</html>


main.js 

import store from "./store.js";


new Vue({

  store,

  computed: Vuex.mapState(['num']),

  methods: {

    increment() {

      this.$store.commit('INCREMENT', 10)

    }

  }

}).$mount('#app')


store.js 

export default new Vuex.Store({

  state: {

    num: 0

  },

  mutations: {

    INCREMENT (state, payload) {

      state.num += payload

    }

  }

});


출력 결과 

img57 


mutations에 선언된 함수는 1번째 매개변수로 스토어의 상태를, 2번째 매개변수로 payload를 전달받습니다.


컴포넌트에서 변이에 접근하려면 this.$store.mutations와 같은 직접적인 접근은 불가하며, 스토어의 commit 메소드를 실행시켜야 합니다. 1번째 매개변수로 변이 함수 이름(액션 타입)을, 2번째 매개변수로 payload를 전달합니다.


변이 역시 Vuex에서 mapMutations 헬퍼 함수를 제공하고 있으므로 다음과 같이 작성할 수 있습니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: Vuex.mapState(['num']),

  methods: {

    ...Vuex.mapMutations(['INCREMENT']),

    increment() {

      this.INCREMENT(10)

    }

  }

}).$mount('#app')



4) 액션(Actions)


변이 안에서 중요한 사실은 반드시 동기적(Synchronized)이어야 한다는 것입니다. Vue의 공식 디버그 툴인 Vue Devtools는 UI를 통해 변이를 추적하여 스토어의 상태를 확인할 수 있는데, 이때 변이의 추적은 commit 메소드의 호출을 기준으로 합니다.


만약 변이를 비동기(Asynchronized) 함수로 작성한다면 commit 시점과 콜백 시점이 다르기 때문에 어플리케이션의 상태 추적을 힘들게 만들 수 있습니다.


아래와 같이 INCREMENT 변이 함수에 setTimeout을 통해 비동기적으로 상태를 변경시켜 보겠습니다.


store.js

export default new Vuex.Store({

  state: {

    num: 0

  },

  mutations: {

    INCREMENT (state, payload) {

      setTimeout(function() {

        state.num += payload

      }, 1000)

    }

  }

});


출력 결과를 확인하면 Vue Devtools에서 스토어 상태를 제대로 반영하지 못하는 것을 확인할 수 있습니다.


출력 결과 

img58 


이를 해결하기 위한 기능이 액션(Actions)입니다.


액션 함수를 비동기로 작성하여 콜백 함수에서 commit를 통해 변이 함수를 호출하여 스토어 상태를 변경할 수 있습니다.


main.js

import store from "./store.js";


new Vue({

  store,

  computed: Vuex.mapState(['num']),

  methods: {

    increment() {

      this.$store.dispatch('increment', 10)

    }

  }

}).$mount('#app')


store.js 

export default new Vuex.Store({

  state: {

    num: 0

  },

  mutations: {

    INCREMENT (state, payload) {

        state.num += payload

    }

  },

  actions: {

    increment (context, payload) {

      setTimeout(function() {

        context.commit('INCREMENT', payload)

      }, 1000)

    }

  }

});


출력 결과 

img59 


actions에 선언된 함수는 1번째 매개변수로 스토어 context 자체를, 2번째 매개변수로 payload를 전달받습니다.


컴포넌트에서 액션에 접근하려면 스토어의 dispatch 메소드를 실행시켜야 합니다. 1번째 매개변수로 액션 함수 이름을, 2번째 매개변수로 padyload를 전달합니다.


변이 역시 Vuex에서 mapActions 헬퍼 함수를 제공하고 있으므로 다음과 같이 작성할 수 있습니다.


index.html

<!DOCTYPE html>

<html lang="ko">

<head>

  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

  <script src="https://cdn.jsdelivr.net/npm/vuex@3/dist/vuex.js"></script>

  <script defer type="module" src="./main.js"></script>

  <title>Vue 연습</title>

</head>

<body>

  <div id="app">

    <div>{{ num }}</div>

    <button @click="incrementHandler">증가</button>

  </div>

</body>

</html>


main.js 

import store from "./store.js";


new Vue({

  store,

  computed: Vuex.mapState(['num']),

  methods: {

    ...Vuex.mapActions(['increment']),

    incrementHandler() {

      this.increment(10)

    }

  }

}).$mount('#app')

#vue.js# vuex# flux# state# action# mutation# getters# store# dispatch# commit
댓글
자동등록방지
(자동등록방지 숫자를 입력해 주세요)