Vue 2.x 버전을 썼던 사용자라면 Vuex 상태관리 라이브러리를 알 것이다. Nuxt 에서 SSR이 제공되고 Vue 3 에서는 Composite API 가 사용되면서 새로운 라이브러리로 대체되었다.
복잡한 어플리케이션을 작성하다보면 컴포넌트 혹은 페이지간 동일한 상태(값)에 대한 접근이 필요할 때가 있다. Nuxt 는 useState 라는 SSR 친화적인 상태관리 컴포저블을 제공하고 있다.
자세한 내용은 Nuxt-개요 > 11. 상태관리 를 참고하자.
전역 상태 정의하기
useState 는 상태 참조를 키 값을 통해서 한다. 여기서는 userState 를 이용해 사용자 정의 상태관리자를 생성한다.
프로젝트 루트에서 /composable/states.ts 파일을 생성하고 아래와 같이 코딩한다.
/composables/states.ts
export const useCounter = () => useState<number>("couter", () => 0);
export const useColor = () => useState<string>("color", () => 'pink');
위 상태 관리자를 페이지에서 호출해보자. /pages/states.vue 파일을 생성하고 아래와 같이 코딩한다.
<script setup lang="ts">
const counter = useCounter();
const color = useColor();
</script>
<template>
<div>
Counter: {{ counter }}
<button class="bg-green-500 text-white px-2 mx-2 rounded" @click="counter++">
+
</button>
<button class="bg-red-500 text-white px-2 mx-2 rounded" @click="counter--">
-
</button>
</div>
<div>
Color: <span :style="{backgroundColor: color}">{{ color }}</span>
<div :style="{backgroundColor: color}">
Background Color
</div>
</div>
</template>
http://localhost:3000/states 화면을 열어서 '+', '-' 버튼을 눌러보자. 상태 값이 변하는 걸 확인할 수 있다.
복잡한 상태관리
어플리케이션 복잡도가 올라가면서 전역상태의 복잡도도 증가할 수 있다. useState 만으로도 충분하지만, 섬세한 상태관리가 필요할 때가 있다. 예를 들면 상태에 접근하는 행위를 제한하고 싶을 때 상태제어 행위(action) 을 정의해서 상태관리를 제한함과 동시에 정교하게 제어할 수 있다. 이럴 때는 Nuxt 에서 공식적으로 권장하는 피니아를 사용하자.
피니아를 사용한 상태관리
위 예제 couter 상태를 pinia 로 대체해보자.
설치
yarn add @pinia/nuxt
# or with npm
npm install @pinia/nuxt
모듈 설정
nuxt.config.ts 에서 modules 속성에 pinia 모듈 사용을 추가한다.
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
css: [
"~/assets/main.css"
],
modules:[
'@pinia/nuxt'
],
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
})
pinia 스토어 생성
프로젝트 루트에서 stores/ 디렉터리를 생성하고, 해당 디렉터리에서 counter.ts 파일을 생성해서 아래와 같이 코딩한다.
/stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// could also be defined as
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
},
})
couterStore 를 쓸 수 있게 states.vue 소스를 변경하자.
/pages/states.vue
<script setup lang="ts">
import {useCounterStore} from "~/stores/counter";
const store = useCounterStore();
const color = useColor();
</script>
<template>
<div>
Counter: {{ store.count }}
<button class="bg-green-500 text-white px-2 mx-2 rounded" @click="store.increment()">
+
</button>
<button class="bg-red-500 text-white px-2 mx-2 rounded" @click="store.decrement()">
-
</button>
</div>
<div>
Color: <span :style="{backgroundColor: color}">{{ color }}</span>
<div :style="{backgroundColor: color}">
Background Color
</div>
</div>
</template>
결과는 아래와 같이 동일하다.
아래 임포트 문이 추가 되었다.
import {useCounterStore} from "~/stores/counter";
상태관리자로 pinia 를 사용하기로 결정했다면 stores/ 디렉토리에 있는 스토어들을 자동으로 임포트 하도록 구성할 수 있다. nuxt.config.ts 를 아래와 같이 수정하자.
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
...
modules: [
'@pinia/nuxt'
],
imports: {
dirs: ['./stores/**'],
},
...
})
소스가 아래처럼 깔끔해졌다.
<script setup lang="ts">
const store = useCounterStore();
const color = useColor();
</script>
<template>
<div>
Counter: {{ store.count }}
<button class="bg-green-500 text-white px-2 mx-2 rounded" @click="store.increment()">
+
</button>
<button class="bg-red-500 text-white px-2 mx-2 rounded" @click="store.decrement()">
-
</button>
</div>
...
</template>