NodeJS

[NodeJS/VueJS] Vuex 설치 및 사용 #1 - 기초편

Question영 2019. 11. 1. 10:10
반응형

오늘도 미래에 공부한 개념을 잊은 저를 위해 개념 정리 포스팅을 작성해보도록 하겠습니다.

 

Vue 를 이용하여 프론트 작업을 하다 보면 이런경우 있으실겁니다.

 

 

  1. Component 별 데이터 공유 문제
  2. 공통 상태에 대한 관리

 

방법은 찾아보면 있을 겁니다.

 

1번은 EventBus 를 이용하여 사용하는 방법이 있고

 

2번은 window 객체를 이용하면 어찌어찌 되겠죠

 

하지만 EventBus 를 이용하면 프로젝트가 커질수록 이벤트 추적이 힘들어져 유지보수가 힘들어 지는 단점이 있고

 

window 객체를 이용하는 것은 어찌 성공한다고 해도 모양새가 그리 좋아보이지 않을것 같습니다.

 

방법을 찾다가 이런 문제를 해결해주는 Vuex 라는 라이브러리를 찾았습니다.

 

Vuex 의 공식 홈페이지에서 설명하고 있는 이 라이브러리의 정의는 다음과 같습니다.

 

Vuex는 Vue.js 애플리케이션에 대한 상태 관리 패턴 + 라이브러리 입니다. 

 

이제부터 Vuex 의 설치와 어떤식으로 활용하는지 간략하게 살펴보도록 하겠습니다.

 

시스템 환경

 

 

설치

 

NPM 이 설치되어 있다는 전제하에 진행하도록 하겠습니다.

 

콘솔이나 터미널 창에 다음과 같이 입력하고 실행해주세요.

 

npm i vuex --save

 

기본 개념

 

상태 관리라는 말이 이해가시는 분들도 있지만 '그게 뭐지?' 라고 이해가 안가시는 분들도 계실겁니다.

 

공식 홈페이지 내용을 살펴보다보면 좀 더 풀어 놓은 설명이 있습니다.

 

공유된 상태 관리를 처리하는 데 유용

 

페이지가 변경되어도 공통으로 상태값을 들고 있어야 하는 변수나,

 

공통으로 사용되는 로직 공통 상태값을 수정하는 로직들을 공유하여

 

코드를 가독성있게 구성할 수 있게 도와줍니다.

 

한마디로! 처음 제가 말씀 드렸던 두가지 문제 상황을 해결하는데 도움을 줍니다.

 

만약 공통으로 사용하는 Util 성 로직들을 관리하고자 하신다면 믹스인 기능을 추천드립니다.

 

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

 

Vue.js 구성하고 있는 요소를 살펴볼수 있는 간단한 예제입니다.

 

이 예제를 보시면 상태관리 요소는 다음과 같습니다.

 

  • state : 컴포넌트 간 공유될 data
  • view : 데이터가 표현될 template
  • actions : 사용자의 입력에 따라 반응할 methods

 

 

 

Vuex 는 이 3가지의 요소 중 공통으로 사용되는 모듈을 지정하여 효율적으로 관리를 도와줍니다.

 

 

기본 구조

 

Sample 프로젝트는 다음과 같은 구조로 구성해봅시다.

 

간단하게 Counter 앱을 만들어 볼겁니다.

 

우선 Store 라는 폴더를 생성하시고 그 안에 index.js 파일을 생성하여 다음과 같이 구성해주세요.

 

 

# store/index.js

/* eslint-disable eol-last */
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {}
})

 

Vuex 의 가장 기본이되는 구조입니다.

 

공통으로 관리하는 모듈과 변수를 store 라는 Vuex 객체로 4가지 개념을 통하여 관리하게 됩니다.

 

  • State : 컴포넌트간 공유할 Data 객체를 의미
  • Getters
    • 공유하는 Data 를 그대로 전달, 혹은 가공하여 전달
    • 쉽게 다른 언어에서도 사용하고 있는 Getter 같은 역할이라고 생각하면 편함
  • Mutations
    • 저장소에 공유되어 있는 Data 를 가공
    • 인자를 전달받아 미리 정의된 모듈을 실행하여 상태를 재 변경
    • Setter 역할이라고 생각하면 편함
  • Actions
    • Mutations 과 동일하나 비동기 작업을 처리할때 사용
    • 주로 네트워크 통신을 통한 API 호출, 반응형 동작시 사용

 

활용

 

해당 파일의 내용을 다음과 같이 구성해봅시다.

 

# store/index.js

/* eslint-disable eol-last */
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store({
  state: { // 공통 관리되는 상태값을 관리
    counter: 0
  },
  getters: { // 공유되는 상태 값을 조회 로직을 관리
    getCounter (state) {
      return state.counter
    }
  },
  mutations: { // 상태 값을 변경하는 로직을 관리
    addCounter: function (state, payload) {
      console.log(payload)
      state.counter++
    },
    subCounter: function (state, payload) {
      state.counter--
    },
    doubleCounter: function (state, paload) {
      state.counter = state.counter * 2
    }
  },
  actions: { // 비동기 통신 및 동작을 정의하고 관리
    subCounter: function ({commit, state}, payload) {
      return commit('subCounter')
    },
    asyncAddCounter: function (context, palyload) {
      return setTimeout(() => {
        context.commit('addCounter')
      }, 1000)
    }
  }
})

 

각 목적에 맞게 객체들을 구성해보았습니다.

 

state 안의 변수를 조회, 수정, 비동기 처리 하는 방법은 보시는 예제와 같이 다양하게 처리가 가능합니다.

 

공통 상태값을 조회하는 모듈을 정의했으니 이제 vue 컴포넌트에서 호출하는 방법도 살펴보도록 하겠습니다.

 

# HelloWorld.vue

<template>
  <div class="hello">
    Parent counter : {{ getCounter }} <br>
    <button @click="addCounter">+</button>
    <button @click="subCounter">-</button>
    <button @click="doubleCounter">*</button>
    <button @click="asyncAddCounter">~+</button>

    <!-- Child 컴포넌트를 등록하고 counter 데이터 속성을 props 로 전달한다. -->
    <!-- <child v-bind:passedCounter="counter"></child> -->
    <child></child>
  </div>
</template>

<script>
import Child from './Child.vue'
import {mapGetters, mapMutations, mapActions} from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'getCounter'
    ])
  },
  methods: {
    // 이벤트 추가
    addCounter () {
      this.$store.commit('addCounter')
    },
    subCounter () {
      // this.$store.state.counter--
      this.$store.dispatch('subCounter')
    },
    ...mapMutations([
      'doubleCounter'
    ]),
    ...mapActions([
      'asyncAddCounter'
    ])
  },
  components: {
    // Child 컴포넌트를 하위 컴포넌트로 등록
    'child': Child
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

 

# Child.vue

<template>
  <div>
    <div>
      <hr>
      Child counter : {{ childCounter }} <br>
      <button @click="addCounter">+</button>
      <button @click="subCounter">-</button>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Child',
  computed: {
    childCounter () {
      return this.$store.getters.getCounter
    }
  },
  methods: {
    // 이벤트 추가
    addCounter () {
      this.$store.commit('addCounter')
    },
    subCounter () {
      // this.$store.state.counter--
      this.$store.dispatch('subCounter')
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

 

4가지 개념을 활용하는 방법을 차례대로 보도록 하죠

 

 

  • State

store 에 있는 공통 상태 값인 counter 를 조회 하기 위해서 다음과 같이 접근하면 됩니다.

this.$store.state.counter

이것을 {{}} 이중괄호를 사용하거나 Method 를 이용하여 조회를 하면 됩니다.

// 템플릿에서 사용할 경우

<template>
  <div>
    ...
        Parent counter : {{ this.$store.state.counter }} <br>
    Parent counter : {{ getCounter() }} <br>
    ...
  </div>
</template>
<script>
export default {
  ...
  methods: {
    // 이벤트 추가
    getCounter () {
      return this.$store.state.counter
    },
    ...
  }
}
</script>

 

물론 조회만 되는 것이 아닌 데이터의 수정도 가능합니다.

 

하지만 공용으로 관리하는 데이터이기 때문에 한군데에서만 수정에 관여하면 문제가 없지만

 

여러 컴포넌트에서 수정하는 로직을 넣게 되면 프로젝트가 커질수록 관리에 대한 문제점이 생기게 됩니다.

 

  • Getter
// HelloWorld.vue, Child.vue

<template>
  <div>
    ...
    counter : {{ getCounter() }} <br>
    ...
  </div>
</template>
<script>
export default {
  ...
  methods: {
    // 이벤트 추가
    getCounter () {
      return this.$store.state.counter++
    },
    ...
  }
}
</script>

 

예를 들면 동일한 위의 예제 코드와 같이 동일하게 getCounter 매서드를 호출하는 로직을 구성한다고 합시다.

 

만약에 증가하는 로직이 감소하는 로직으로 변경되어야 하면

 

각 페이지의 Method 의 getCounter 에서 로직을 변경해줘야 하죠.

 

단순히 두 컴포넌트에서만 국한되는 혹은 작은 프로젝트에서는 이런 경우 별 문제가 없을것이나

 

프로젝트가 확장되고 관리되어야 하는 소스 규모가 커지면 커질수록

 

이런식의 로직은 개발과 관리가 점점 힘들어질것은 당연할 것입니다.

 

이렬경우 공통의 모듈을 정의하고 조회하는게 좋은데 그 역할을 Getter 에서 해줍니다.

// store/index.js
...
export const store = new Vuex.Store({
  state: { // 공통 관리되는 상태값을 관리
    counter: 0
  },
  getters: { // 공유되는 상태 값을 조회 로직을 관리
    getCounter (state) {
      return state.counter++
    }
  },
  ...
})
// HelloWorld.vue, Child.vue

<template>
  <div>
    ...
    counter : {{ getCounter() }} <br>
    ...
  </div>
</template>
<script>
export default {
  ...
  methods: {
    // 이벤트 추가
    getCounter () {
      return this.$store.getters.getCounter
    },
    ...
  }
}
</script>

 

store 에서 getters 라는 객체로 Getter 함수들을 관리하고 있으며

 

위에서 주요하게 봐야 할 로직은 getCounter 부분입니다.

 

이런식으로 구성하면 공통으로 사용하는 조회하는 로직을 한곳에서만 수정하면 다른곳에도 자동으로 적용되니

 

이벤트 추적 및 관리적인 측면에서 좀 더 효율적이게 됩니다.

 

 

  • Mutation

Getter 의 경우 주어진 공통 상태값을 조회하는 역할 이라고 하면

 

Mutation 은 공통 상태값을 수정하는 역할 이라고 볼수 있습니다.

 

Getter 와 다르게 인자 값을 전달 받아 상태를 갱신할수 있습니다.

// store/index.js

...
state: { // 공통 관리되는 상태값을 관리
  counter: 0
},
...
mutations: { // 상태 값을 변경하는 로직을 관리
  addCounter: function (state, payload) {
    console.log(payload)
    state.counter++
  },
  subCounter: function (state, payload) {
    state.counter--
  },
  doubleCounter: function (state, paload) {
    state.counter = state.counter * 2
  }
}
...

 

store 에서 mutations 라는 객체로 Mutation 함수들을 관리하고 있습니다.

 

정의된 인자 파라미터를 잠깐 살펴봅시다.

 

state : 내부 공통 상태값 객체

- 위의 예제를 보시면 state 를 통해 공통 상태값인 counter 에 접근하고 있습니다.

 

payload : 전달 받는 인자 객체

- 말 그대로 전달 받는 객체 입니다.

 

단순히 1이나 10 이란 정수 값을 전달한다고 한다면

 

10 자체가 단순 객체로 payload 를 조회 했을 경우 10 이란 값을 조회 할 수 있습니다.

 

하지만 객체이기 때문에 다음과 같은 객체를 전달할수도 있습니다.

 

{ name: "카운트", value: 10} 

 

이럴경우 이런식의 조회도 가능합니다.

 

addCounter: function (state, payload) {
    console.log(payload.name, palyload.value)
    state.counter++
},

 

전달해야할 인자가 여러개일 경우 이런식으로 조회하여 상태값에 반영하시면 됩니다.

 

<template>
  <div class="hello">
        ...
    <button @click="addCounter">+</button>
        ...
  </div>
</template>
<script>
...
export default {
  ...
  methods: {
    // 이벤트 추가
    addCounter () {
      this.$store.commit('addCounter', 10)
    },
    ...
  },
  ...
}
</script>

 

전체 예제 코드 중 위의 부분이 Vue 파일에서 mutations 객체를 호출하여 사용하는 내용입니다.

 

예제에서는 템플릿 사용을 위해 methods 에 함수를 생성하여 store 연결 사용했습니다.

 

mutations 객체 호출은 commit 을 이용해야 합니다.

 

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

 

첫번재 인자값은 호출하는 함수 이름을 입력 받고

 

두번째 인자값은 호출 함수에 전달하는 인자 객체를 입력합니다.

 

위에서 잠깐 언급한 두개이상의 데이터를 전달하기 위해서는 다음과 같이 작성하시면 됩니다.

 

this.$store.commit('addCounter', { name: "카운트", value: 10})

 

 

  • Action

웹 화면을 구성할때 반응형(Reactive) 동작을 구현하거나 네트워크 통신을 하는 등의 비동기적인 작업을 정의하여 수행합니다.

 

/* eslint-disable eol-last */
...
export const store = new Vuex.Store({
    ...
  actions: { // 비동기 통신 및 동작을 정의하고 관리
    subCounter: function ({commit, state}, payload) {
      return commit('subCounter')
    },
    asyncAddCounter: function (context, palyload) {
      return setTimeout(() => {
        context.commit('addCounter')
      }, 1000)
    }
  }
})

 

actions 라는 객체로 관리하며 Mutation 과 연계하여 데이터를 처리합니다.

 

위의 코드에서 actions 의 함수들을 보시면 commit 을 사용하는 것을 보실수 있을 것입니다.

 

commit 을 어디에서 많이 보셨죠?

 

바로 Mutation 이 관리하고 있는 함수를 사용하기 위한 방법입니다.

 

쉽게 비동기적인 처리만 action 에서 담당하고 실제 처리 mutation 을 연결하여 처리한다고 보시면 되겠네요.

 

그리고 특이점 하나 더!

 

function ({commit, state}, payload) {}
function (context, palyload) {}

 

Action 에서 관리하는 함수들의 입력받는 인자들은 Mutation 과 비슷하게 두개입니다.

 

context에는 state, commit, dispatch, rootstate와 같은 속성들을 포함합니다.

 

여기서 대부분 눈치 채셨겠지만 이용어들 중 state, commit 는 어디선가 많이 보셨죠?

 

바로 앞전에 설명한 State 와 Mutation 에서 정의하고 관리하고 있는 기능들을 사용하기 위한 함수입니다.

 

그럼 나머지 dispatchrootstate 는 어디서 사용될까요?

 

이 4가지 속성들을 간략하게 정리만 하고 나중에 자세히 설명드리겠습니다.

 

state : State 에서 관리하는 공통 상태값

commit : Mutation 에서 정의하고 관리하고 있는 기능들을 사용하기 위한 함수

dispatch : Action 에서 정의하고 관리하고 있는 기능들을 사용하기 위한 함수

rootstate : 모듈에서 나오는 개념으로 모듈의 상위(store) State에 접근하여 정의된 공통 상태값을 조회, 수정 할수 있는 객체

 

Action 에서 정의하는 함수에서 첫번째 인자에 context 만 정의하고 활용해도 되지만

 

위의 예제와 같이 context 에 정의된 객체 형태로 풀어서 좀 더 직관적으로 정의해도 상관없습니다.

 

두번째 인자는 Mutation 과 동일하게 payload 를 받고 있습니다.

 

내용도 동일하게 단일 인자 값이나 객체 형태로 받아 사용할수 있습니다.

 

// HelloWorld.vue

...
<script>
...
export default {
     ...
  methods: {
    // 이벤트 추가
    addCounter () {
            this.$store.commit('addCounter')
    },
    subCounter () {
      this.$store.dispatch('subCounter')
    },
    ...
}
</script>
...

 

자 이제 vue 에서 사용 방법을 살펴보도록 하겠습니다.

 

앞서 잠깐 언급한적이 있는 것 처럼 dispatch 를 사용하시면 됩니다.

 

사용 방법은 Mutation 의 commit 과 비슷합니다.

 

this.$store.dispatch('[사용할 Action 함수 이름]', 단일 인자 혹은 객체)

 

다시 한번 리마인드하자면!

 

단일 인자는 1, 1.0, '텍스트' 등의 타입의 데이터 형태, 객체는 {name: '텍스트', value: 10, ...} 형태를 말합니다.

 

잊지마세요.

 

작성하다 보니 내용이 길어졌네요.

 

제일 처음 예제를 보시면 보이는 mapGetters, mapMutations, mapActions 의 내용 설명이 남았고

 

예제에는 없지만 모듈별 관리하는 방법, 폴더 및 함수명 정리 방법에 대한 내용이 남았습니다.

 

이 부분 다음 포스팅에서 다루도록 하겠습니다.

 

긴글 봐주셔서 감사하며 개념적으로 수정할 내용이 보이시는 분이 있으시면 댓글로 의견주세요.

 

감사합니다.


참고 자료

반응형