Nuxt.js에서 Vuex(Store) 사용 하기

Nuxt.js·2021년 8월 26일
3

NUXT기초 배우기

목록 보기
9/20

스토어?

웹 프로젝트를 진행 하다보면 점점 규모가 커짐에 따라 이런 저런 데이터를 저장해야 한다. 유저의 세션 정보 라던지, 게시판의 글이나 댓글 등 Frontend 측의 로컬 환경에 저장 하며, 모든 페이지들에서 자유롭게 읽고 쓸 수 있는 저장소가 스토어이고 이를 관리 해주는 Vue.js 의 라이브러리중 하나가 Vuex이다. (개발코스트/SW퍼포먼스 측면에서 trade-off 관계이기 때문에 무작정 Vuex를 도입 할 필요는 없다. 소규모 프로젝트에서는 컴포넌트 내에 로컬 저장소 만으로 충분할 것이다.)

스토어 폴더

store 디렉토리는 Vuex Store관련 파일을 담고 있다. (복잡한 config 재설정 과정 없이는 이름 변경이 불가능하다) 근데 Nuxt.js로 새 프로젝트를 생성 하는 것 만으로는 Vuex를 사용 할 수 없다. 처음에는 기본값으로 비활성화 되어 있기 때문이다. store 디렉토리에 index.js파일을 작성 하면 자동으로 활성화 된다.

스토어 활성화 시키기

Nuxt.js는 store폴더를 모니터링 하고 있기 때문에, 숨김파일과 README.md파일을 제외한 다른 파일을 아무거나 추가 하면 스토어가 활성화 될 것이다. 내부적으로는 다음의 2가지 이벤트가 Nuxt.js 안에서 일어난다.

  • import Vuex
  • store 옵션을 Vue인스턴스에 추가

모듈 작성

저장 해야할 파일을 단일 스토어로 구성 할 수도 있지만 프로젝트 규모가 커질수록 용도에 따라 데이터를 나누어 저장 하는 것이 유지보수와 개발 측면에서 편리할 것이다. 예를들면 유저 정보만을 따로 스토어1에 담고 게시판과 그 댓글들을 스토어2에 담는 식이다. 이를 네임스페이스모듈(namespaced module)이라 한다.
그러므로 스토어 모듈은 여러개 존재할 수 있다. store/index.js파일이 기본적으로 '루트모듈' 이 된다.
그리고 각 모듈에 저장 하는 지역변수값이 되는 state는 함수형태로 구성 해야 한다. 그래야만 서버사이드의 레이스컨디션을 막을 수 있다.

코드를 살펴보기 위해
아래 예제처럼 store/index.js 파일을 만들어 보자.
동작을 알아보기 위해 카운터 변수를 넣고 0으로 초기화 했으며 1씩 증가시키는 뮤테이션 함수를 하나 추가 한다.

export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state) {
    state.counter++
  }
}

그리고 todo list(할일 목록)에 해당 하는 store/todos.js파일을 만들자.
할일 목록에 해당하는 list 배열을 state안에 가지고 있으며 추가/삭제 그리고 확인 여부를 토글 하는 함수를 추가 한다.

export const state = () => ({
  list: []
})

export const mutations = {
  add(state, text) {
    state.list.push({
      text,
      done: false
    })
  },
  remove(state, { todo }) {
    state.list.splice(state.list.indexOf(todo), 1)
  },
  toggle(state, todo) {
    todo.done = !todo.done
  }
}

그럼 넉스트.js가 알아서 아래와 같은 형태로 재컴파일 해준다.

new Vuex.Store({
  state: () => ({
    counter: 0
  }),
  mutations: {
    increment(state) {
      state.counter++
    }
  },
  modules: {
    todos: {
      namespaced: true,
      state: () => ({
        list: []
      }),
      mutations: {
        add(state, { text }) {
          state.list.push({
            text,
            done: false
          })
        },
        remove(state, { todo }) {
          state.list.splice(state.list.indexOf(todo), 1)
        },
        toggle(state, { todo }) {
          todo.done = !todo.done
        }
      }
    }
  }
})

루트 모듈로 counter 가 있으며 그안에 네임스페이스로 나뉘어지는 추가 모듈들 중 하나로 todos가 있는 것을 알 수 있다.

이를 실제 Vue.js 컴포넌트(페이지) 에서 사용 하는 예제는 아래와 같다.

<template>
  <ul>
    <li v-for="todo in todos" :key="todo.text">
      <input :checked="todo.done" @change="toggle(todo)" type="checkbox">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
    </li>
    <li><input @keyup.enter="addTodo" placeholder="할일 목록 입니다."></li>
  </ul>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  computed: {
    todos () {
      return this.$store.state.todos.list
    }
  },
  methods: {
    addTodo (e) {
      this.$store.commit('todos/add', e.target.value)
      e.target.value = ''
    },
    ...mapMutations({
      toggle: 'todos/toggle'
    })
  }
}
</script>

<style>
.done {
  text-decoration: line-through;
}
</style>

Store에 사용 되는 소스코드는 index.js 파일 하나에 몰아서 넣어도 되며 혹은 4개의 actions.js, getters.js, index.js,mutations.js 파일로 각각 나누어서 작성 해도 자동으로 인식 된다. 모듈은 폴더로 나누면 되며 루트 모듈도 마찬가지이다. 유지보수 측면에서는 나누는 것이 약간 유리 할 것이다. 자신의 프로젝트 규모에 맞게 적절하게 구성 하면 된다.

폴더 구조

  • index(루트모듈) + ui 관련 을 넣고 쇼핑몰-카트, 쇼핑몰-상품목록(카테고리별로 나눔)을 서브모듈로 넣는다.
    위와 같은 사양이 주어졌다고 하고 폴더구성을 생각해보면다면 아래와 같을 것이다. 물론 폴더명 등은 자유롭게 정하면 된다.
store/
--| index.js
--| ui.js
--| shop/
----| cart/
------| actions.js
------| getters.js
------| mutations.js
------| state.js
----| products/
------| mutations.js
------| state.js
------| itemsGroup1/
--------| state.js

플러그인

필요에 따라 Vuex에 딸린 추가 플러그인을 설치 해야 할 경우가 있을 것이다. 예를 들면 창을 껏다 켜도 데이터가 유지되게 한다던가 (Vuex-Persistedstate), 탭을 여러개 열어도 데이터가 공유되게 한다던가 (Vuex-Multi-tab-state) 등이 있을 수 있다. 플러그인 추가는 store/index.js파일에 아래와 같은 형식으로 할 수 있다.

import myPlugin from 'myPlugin'

export const plugins = [myPlugin]

export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state) {
    state.counter++
  }
}

서버초기화 액션

스토어의 모드를 universal로 설정 한 경우, 루트모듈의 actions 안에 nuxtServerInit() 함수를 정의 한다면 서버사이드에서 클라이언트사이드로 다이렉트로 데이터를 줄 수 도 있다. (Vue.js의 SSR을 말 하는 것으로, Backend API서버와 혼동 하지 않도록 주의 한다.)
예를 들어 유저세선을 서버사이드에 유지 하고 있고 리퀘스트의 session 안에 user를 저장 해 놓았다고 가정 하면, 아래와 같은 형태로 클라이언트-사이드로 user 객체를 넘길 수 있다.

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}

nuxt generate를 통해 컴파일을 한 경우 모든 동적 페이지에 접근 할 때마다 nuxtServerInit() 함수가 불릴 것이다. 이때는 아래와 같이 async/await 키워드를 추가 해주면 해결 된다.

actions: {
  async nuxtServerInit({ dispatch }) {
    await dispatch('core/load')
  }
}

엄격모드

Vuex Store에서 Getter/Setter 패턴을 엄격하게 지키는 것을 엄격 모드라고 한다. 엄격모드가 켜져 있는 상태에서 Mutations 가 아닌 곳에서 state를 건드리면 에러가 리턴된다.

엄격모드는 개발환경에서 자동으로 켜지고 프로덕션 모드로 가면 꺼지는데, 만약 개발 모드에서도 스트릭트 모드를 해제 하고 싶다면 store/index.js파일에 아래와 같이 변수를 하나 선언 해주면 된다.

export const strict = false

이상으로 Nuxt.js 에서 Vuex를 이용해서 데이터를 저장하는 기본적인 방법을 알아 보았다. 다음 포스팅에서는 실질적인 예제로 API를 사용 해가면서 데이터를 저장 하는 방법을 알아보자.

profile
Nuxt.js

0개의 댓글