본문 바로가기

FrontEnd/Vue

[FE] Vuex 시작하기 - 개념과 핵심 속성

 

Vue를 사용하여 프로젝트를 할 때, Vuex를 사용하게 되는 데 사용하는 이유와 Vuex가 무엇인지
이번 포스팅에서 정리해보자.

Vuex 목차

1. Vuex 란?
2. Vuex는 언제, 왜 필요한가
3. Vuex 설치하기
4. Vuex의 핵심 속성
1) State
2) Getters
3) Mutations
4) Actions
5) Modules

1. Vuex 란?

Vuex는 한마디로 말하자면 상태(STATE)를 관리하는 라이브러리이다.

여기서 상태란 무엇일까?

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

여기 간단한 Vue.js 앱이 있다.

  • 상태 는 앱을 작동하는 원본 소스.
  •  는 상태의 선언적 매핑.
  • 액션 은  에서 사용자 입력에 대해 반응적으로 상태를 바꾸는 방법.

여기서 data() 를 우린 "상태(state)" 라고 부른다. (컴포넌트 간 공유할 수 있는 데이터)
바로 이 상태를 관리하는 것이 Vuex 이다.

 

 주의  
아래에서 설명하겠지만 Vuex 는 store(저장소)를 가지고 있다.즉, 상태(state)의 저장소를 가지고 있는 것인데, 이 저장소라는 말 때문에 Vuex가 세션/쿠키 localStorage 처럼 브라우저가 닫히지 않는 이상 계속 유지되는, Vuex 저장소에 상태(state)가 저장되는 것 처럼 느껴질 수 있다.하지만 Vuex 의 상태는 메모리에 저장되는 것이기 때문에 새로 고침시 초기화 된다.Vuex는 컴포넌트 단위의 data() 가 그저 어플리케이션 단위의 data()가 된 것이라고 보면 된다. data()가 localStorage에 저장되진 않는다. 새로고침하면 당연히 초기화됨..😅

상태를 새로고침 시에도 유지 하고 싶다면 vuex-persistedstate(state - localstorage 동기화 라이브러리) 같은 또 다른 라이브러리가 필요하다. 


2. Vuex는 언제, 왜 필요한가

"단방향 데이터 흐름" 개념의 매우 단순한 도표

prop이나 ref 등으로 컴포넌트 간 데이터(상태)를 공유할 수 있는데 굳이 Vuex가 필요할까?

Vue는 공통의 상태를 공유하는 여러 컴포넌트 가 있는 경우, 단순함이 빠르게 저하된다.

  • 여러 뷰는 같은 상태에 의존한다. 지나치게 중첩된 컴포넌트를 통과하는 prop이 생기게 되면 유지보수하기 힘든 난해한 코드가 될 수 있다. 
  • Vue는 단방향으로 데이터가 흐르기때문에, 여러 컴포넌트가 한 상태를 공유하는 경우, 형제 컴포넌트간의 상태공유/관리가 복잡해질 수 있다. 즉 서로 다른 뷰의 작업은 동일한 상태를 반영해야 할 수 있습니다.

Vue는 단방향으로 데이터가 흐르기때문에, 여러 컴포넌트가 한 상태를 공유하는 경우, 형제 컴포넌트간의 상태공유/관리가 복잡해질 수 있다.

 

3. Vuex 설치하기

https://v3.vuex.vuejs.org/kr/installation.html

 

설치 | Vuex

설치 직접 다운로드 / CDN https://unpkg.com/vuex (opens new window) Unpkg.com (opens new window)은 NPM 기반 CDN 링크를 제공합니다. 위의 링크는 항상 NPM의 최신 릴리스를 가리킵니다. https://unpkg.com/vuex@2.0.0과 같은

v3.vuex.vuejs.org

Vuex는 상태(state)를 저장하고 있는 저장소(store)를 가지고 있다.
Vuex가 설치되었다면 이 저장소를 관리하는 파일을 생성해주자.

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

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
});

/src 경로에 store.js 파일을 만들고 위와 같이 입력해줬다.
Vue.use() 를 통해 Vue가 Vuex 사용함을 선언하고, store 인스턴스를 생성해준 것이다.

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,
}).$mount('#app');

main.js 에선 만들어준 store.js 를 import 해온 뒤 Vue 인스턴스에 주입하자.

 

여기서 잠깐 위 코드에 대해 설명하자면,

Vue.config.productionTip 는 Vue 앱이 처음 실행 될 때 나오는 경고문(배포에 대한 팁)을 출력할 것인지 물어보는 내용이다.
false로 설정하면 경고문(배포에 대한 팁)을 출력하지 않는다.
default 는 false 이다.

 

render: h => h(App)은 네 단계에 거쳐서 ES6로 변형된 메소드이다.

// #1
render: function (createElement) {
    return createElement(App);
}
// #2
render (createElement) {
    return createElement(App);
}
// #3
render (h){
    return h(App);
}
// #4
render: h => h(App);

 

 

정확히 render() 라는 것이 무엇을 의미하는 걸까?

위 코드에서 #1을 보면 render는 실질적으로 createElement()의 반환 값이라는 걸 알수 있다.
여기서 createElement() 란, Virtual DOM(가상 돔)을 만드는 메소드이다.

HTML DOM Node Tree

https://kr.vuejs.org/v2/guide/render-function.html#createElement-%EC%A0%84%EB%8B%AC%EC%9D%B8%EC%9E%90

 

Render Functions & JSX — Vue.js

Vue.js - 프로그레시브 자바스크립트 프레임워크

kr.vuejs.org

요약하자면,
render: h => h(App) 함수는 사실 createElement(App)의 반환 값이고,
createElement()는 가상 돔을 만들어 렌더링 하는 함수이다.
그리고 createElement를 h로 요약한 뒤, ES6의 화살표함수를 쓴 것이 지금의 render: h => h(App) 라는 것이다.
(h 는 hyperscript의 약자로 HTML 구조를 생성하는 스크립트를 의미함.)

store: store 는 Vue에 Vuex(store)를 주입한다는 뜻이다.
마찬가지로 동일한 방법으로 router 도 주입할 수 있다.

4. Vuex의 핵심 속성

Store에는 위에서 봤던 State와 같은 속성이 5개가 존재한다. 

  • state
  • getters
  • mutations
  • actions
  • modules

지금부터 이 속성들을 공부해보자.

1) State

state는 상태(state)의 집합이다.
Vuex 는 단일 상태 트리(single state tree) 를 사용하기 때문에 이 집합 내에서 현재 상태를 쉽게 찾을 수 있다.

  • state는 쉽게 말하면 프로젝트에서 공통으로 사용할 변수를 정의한다.
  • 프로젝트 내의 모든 곳에서 참조 및 사용이 가능하다.
  • state를 통해 각 컴포넌트에서 동일한 값을 사용할 수 있다.
export const state = () => ({
  account: null, 
  isAdmin: null, 
  item: null
});

mapState

computed에 선언된 상태(state)가 많으면, 코드가 반복적이고 장황해질 수 있다.
예를들면, 이런식으로.

computed: {
    count() {
        return this.$store.state.count
    },
    age() {
        return this.$store.state.age
    },
    isStudent() {
        return this.$store.state.isStudent
    }
}

this.$sotre.state 가 반복되는걸 알 수 있다.
이를 해결하기 위해 Vuex는 mapState 라는 Helper를 제공하고 있다.

import { mapState } from 'vuex'
export default {
  // ...
  computed: mapState({
    // #1 화살표 함수로 가져오기
    count: state => state.count,
    // #2 문자열로 가져오기 (`state => state.count`와 같다.)
    countAlias: 'count',
    // #3 `this`를 사용하여 로컬 상태에 액세스하려면 일반적인 함수를 사용해야한다
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
    // #4 this.count를 store.state.count에 매핑한다.
      'count'
  })
}

mapState를 활용해 반복되는 코드를 없애고 state를 깔끔하게 정의할 수 있다.
mapState를 기존 로컬의 computed와 함께 사용하려면 객체 전개 연산자(Object Spread Operator) 를 사용하면 된다. 전개구문이라고도한다 (...)

2) Getters

state를 계산한 값을 사용해야할 때가 있다.

각각의 컴포넌트에서 state를 증가시키면 반복 호출이 잦아져 비효율적인 로직이 된다.


getters는 그런 문제점을 해결해주는 속성이다.
getters는 state에 대한 변이를 각 컴포넌트에서 진행하는게 아니라, Vuex에서 변이를 수행하고 각 컴포넌트에서 state를 호출만 하도록 하게 한다.

  • 각 Components의 계산된 속성(computed)의 공통 사용 정의이다.
  • 여러 Components에서 동일한 computed가 사용 될 경우 Getters에 정의하여 공통으로 쉽게 사용할 수 있다.  
  • 하위 모듈의 getters를 불러오기 위해서는 특이하게 this.$store.getters["경로명/함수명"]; 을 사용해야 한다.
export const store = new Vuex.Store({
    state: {
        count: 0
    },
    getters: {
        increaseCount(state) {
            return ++state.count;
        }
    }
});

mapGetters

state 파트에서 코드가 반복되는 걸 막기 위해 mapState를 사용한다고 했었다.
getters도 마찬가지로 mapGetters를 사용해 코드의 반복을 막을 수 있다.
(이 역시, 다른 computed 속성들과 함께 쓰고싶다면 전개구문을 사용하면 된다.)

computed: {
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
  }

 

3) Mutations

mutations도 getters와 동일하게 state의 값을 변환 시킬 때 사용한다.

  • Mutations의 주요 목적은 state를 변경시키는 역할을 한다. (Mutations을 통해서만 state를 변경해야 함)  
  • 비동기 처리가 아니라 동기처리를 한다. 위의 함수가 실행되고 종료된 후 그 다음 아래의 함수가 실행된다.
  • commit('함수명', '전달인자')으로 실행 시킬 수 있다. mutations 내에 함수 형태로 작성한다

 

mutations와 getters의 차이점

  • mutations는 전달인자를 받을 수 있다.
  • getters는 computed에 등록 했지만 mutations는 methods에 등록한다.

mutations는 동기적 로직을 정의한다는 특징을 가지고 있다.
동기적, 말그대로 순차적으로 변이가 진행된다는 뜻이다.
여러 컴포넌트에서 하나의 상태를 변이시킨다면 현재 state가 어떤지 추적하기 어려울 것이다.
그 때, mutations에 정의한 상태변이함수를 commit 을 사용하여 명시적으로 호출하면 상태를 추적할 수 있다.

예를 통해 알아보자

export const store = new Vuex.Store({
  // ...
  mutations: {
    addCounter: function (state) {
      return state.counter++;
    }
  }
});
<template>
    <div>
        {{ count }}
        <button @click="increaseCnt">+</button>
    </div>
</template>
<script>
    export default {
        computed: {
            count () {
                return this.$store.state.count;
            }
        },
        methods: {
            increaseCnt() {
                this.$store.commit('addCounter');
            }
        }
    }
</script>

mutations의 전달 인자

getters와 mutations의 차이점으로 전달인자가 있다고 했었다.
commit에 추가 전달 인자를 붙여 전달인자를 넘길 수 있다. 이 추가 전달 인자 부분을 payload라고 한다.

 

4) Actions

mutations는 동기적 변이를 다룬다고 하였다.
actions는 그와 반대로, 비동기적 변이를 다루는 속성이다.

주로 setTimeout()이나 서버와의 http 통신 처리와 같이 결과를 받아올 타이밍이 예측되지 않는 로직을 actions에 선언한다.

mutations에서 상태의 변이를 추적하기위해 commit을 사용한다고 했었다.
actions 역시 비동기적 상태를 추적해야하기 때문에 commit을 사용한다.
결국 actions는 정리하자면 mutations의 메소드를 actions에 commit으로 호출하여 비동기 관리를 한다는 것이다.

 

  • Actions의 주요 목적은 Mutations를 실행시키는 역할을 한다.
  • 동기 처리가 아니라 비동기처리를 한다. 순서에 상관없이 먼저 종료된 함수의 피드백을 받아 후속 처리를 하게 된다.
  • dispatch('함수명', '전달인자')으로 실행 시킬 수 있다.
    ex) dispatch('함수명', '전달인자', {root:true})actions 내에 함수 형태로 작성합니다.
  • 비동기 처리이기 때문에 콜백함수로 주로 작성한다.

 

5) Modules

저장소의 규모가 커지면 관리가 힘들어질 수도 있다.
그 때, 우린 저장소를 모듈화 할 수 있다.
각각의 statemutationsgetters 등을 포함하는 저장소 여러개로 나눌 수 있다는 의미이다.

모듈화란?
특정 기준을 두고, 그 기준에 따라 리소스를 단위로 분리하는 것을 의미한다.
예를 들어, 똑같은 메소드 A 를 여러 파일에서 쓰고 있다고 하자.
이 A 메소드를 수정하고 싶다면, A 메소드를 사용 중인 모든 파일에 가서 일일이 수정을 해줘야한다.
A 메소드를 관리하기 굉장히 어려워진다. 이 때 우린 모듈화를 해줄 수 있다.
A 메소드를 Util.js 따위의 공용화 파일에 한번만 정의한 후, A 메소드가 필요하다면 Util.js의 A 메소드를 불러와 사용하는 것이다.
한마디로 어떤 리소스(여기선 A 메소드)를 따로 분리하여 관리하는 것이다.
저장소의 모듈화 같은 경우,
state, getters, mutataions의 크기가 너무 커져서 컴포넌트에서 저장소를 호출할때 무리가 생길 수 있다.
때문에 어떤 우린 기준을 두고 저장소를 쪼갠뒤, 필요시에만 쪼갠 일부 저장소만 호출해서 쓰는 것이다
이 역시 분리해서 관리한다의 모듈화를 의미한다.
const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA'의 상태
store.state.b // -> moduleB'의 상태

이렇게 Vuex의 개념과 핵심 속성을 알아봤다!

Vuex를 사용하여 컴포넌트가 많은 중대형 규모의 SPA에서 유용하게 사용해보자!

 

출처

더보기

https://v3.vuex.vuejs.org/kr/

 

Vuex가 무엇인가요? | Vuex

Vuex가 무엇인가요? Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다. 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역할을 하며 예측 가능한 방식으로 상태를

v3.vuex.vuejs.org

https://doozi0316.tistory.com/entry/Vuex-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%98%88%EC%A0%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0?category=925596 

 

Vuex란? 개념과 예제

📢 들어가기 전에 이번 포스팅에선 Vuex가 무엇인지 알아보고, 간단한 예제를 구현해본다. Vue CLI로 설치한 Vue.js 프로젝트 환경에서 진행했다. Vuex를 시작하기 전에, 아래 방법으로 Vue.js 프로젝트

doozi0316.tistory.com

https://cjw-awdsd.tistory.com/39

 

[Vue] Vuex 간단 설명/예제

Vue 프로젝트를 하다보면 vuex라는 것을 당연히 사용하게 되는데 vuex가 무엇인지 정리하고자 이 글을 쓴다. 1. Vuex vue를 사용하면 부모 컴포넌트의 데이터를 자식 컴포넌트로 보내야할 일이 많다.

cjw-awdsd.tistory.com

https://ux.stories.pe.kr/149

 

개발하면서 경험으로 알게 된 Vuex에서 Store활용 방법

Vue의 개발을 편리하게 도와 주는 공식 툴 중에 Vuex가 있습니다. Vuex의 주요 기능은 개발하는 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저장소 역활 및 관리 입니다. 만약 이게 없다면

ux.stories.pe.kr