useState로 SSR 친화 상태 공유, Pinia 스토어, 컴포저블 분리.
상태 관리
여러 컴포넌트가 같은 데이터를 공유해야 할 때 상태 관리가 필요합니다. Nuxt는 SSR을 고려한 useState 를 기본 제공하며, 더 큰 앱에는 Pinia를 권장합니다. 이번 레슨에서는 두 방법과 컴포저블로 상태를 정리하는 패턴을 다룹니다.
학습 목표
useState가 일반ref와 무엇이 다른지 이해한다.- SSR 환경에서 상태가 안전하게 공유되는 이유를 안다.
- Pinia를 설치하고 스토어를 만든다.
- 컴포저블로 상태 로직을 분리한다.
왜 useState 인가
SSR 환경에서 컴포넌트 밖에 전역 ref 를 두면 모든 사용자가 같은 변수를 공유 해 데이터가 섞일 수 있습니다. useState 는 요청마다 격리된 상태를 만들고, SSR에서 만든 값을 클라이언트로 안전하게 전달합니다.
<script setup>
// 첫 인자는 고유 키, 둘째는 초기값 함수
const count = useState('count', () => 0);
</script>
<template>
<button @click="count++">클릭 {{ count }}</button>
</template>
같은 키 'count' 를 쓰는 다른 컴포넌트는 같은 상태 를 공유합니다.
💡 TIP —
useState는 SSR로 직렬화되므로 함수·클래스 인스턴스 같은 직렬화 불가능한 값은 담지 마세요. 순수 데이터만 담습니다.
컴포저블로 상태 분리
useState 호출을 컴포저블로 감싸면 재사용과 타입이 깔끔해집니다.
// composables/useCounter.ts
export function useCounter() {
const count = useState('count', () => 0);
const increment = () => count.value++;
const reset = () => (count.value = 0);
return { count, increment, reset };
}
<script setup>
// composables/ 안의 함수는 자동 임포트됨
const { count, increment } = useCounter();
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Pinia 도입
상태가 많아지고 액션·게터가 복잡해지면 Pinia 가 더 적합합니다. Nuxt 모듈로 손쉽게 설치합니다.
npx nuxi module add pinia
nuxt.config.ts 에 모듈이 자동 등록됩니다.
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
});
스토어 만들기
stores/ 폴더에 스토어를 정의합니다. 셋업 스타일이 <script setup> 과 가장 잘 어울립니다.
// stores/tasks.ts
export const useTasksStore = defineStore('tasks', () => {
const items = ref([]);
const count = computed(() => items.value.length);
async function load() {
items.value = await $fetch('/api/tasks');
}
function add(title: string) {
items.value.push({ id: Date.now(), title });
}
return { items, count, load, add };
});
<script setup>
const store = useTasksStore();
await store.load();
</script>
<template>
<p>총 {{ store.count }}개</p>
<ul>
<li v-for="t in store.items" :key="t.id">{{ t.title }}</li>
</ul>
<button @click="store.add('새 일감')">추가</button>
</template>
⚠️ 주의 — 스토어 상태를 구조 분해하면 반응성이 끊깁니다. 값만 따로 꺼내려면
storeToRefs(store)를 쓰세요.
useState vs Pinia
| 기준 | useState | Pinia |
|---|---|---|
| 설치 | 내장 (불필요) | 모듈 추가 필요 |
| 적합한 규모 | 가벼운 공유 상태 | 액션·게터 많은 도메인 상태 |
| 구조 | 컴포저블로 직접 정리 | 스토어 단위로 체계화 |
| 개발자 도구 | 제한적 | Vue Devtools 지원 |
작은 앱이나 단순 공유 값은 useState, 비즈니스 로직이 모이는 큰 앱은 Pinia가 무난합니다.
요약
useState는 요청마다 격리되고 SSR로 전달되는 공유 상태입니다.- 같은 키를 쓰면 여러 컴포넌트가 상태를 공유합니다.
- 컴포저블로
useState를 감싸면 재사용성이 좋아집니다. - 큰 앱에서는
@pinia/nuxt모듈과defineStore로 체계적으로 관리합니다.
연습문제
useState('theme', () => 'light')로 다크/라이트 테마 상태를 만들고 토글 버튼을 붙여 보세요.composables/useCounter.ts를 만들어 두 컴포넌트에서 같은 카운터를 공유하게 해보세요.- Pinia를 설치하고
stores/tasks.ts로 일감 목록 스토어를 만들어 보세요. storeToRefs를 써서 스토어의count를 반응성을 유지한 채 꺼내 화면에 표시해 보세요.
힌트 — 전역
ref대신useState(키 필요), 큰 상태는defineStore를 떠올리세요.
💡 연습문제 풀이
불러오는 중…
댓글 0
“Nuxt.js” 강좌에 대한 댓글입니다.