
정처기 실기를... 망하고 난 뒤의 수업. 이번에는 pinia에 대해서 배운다. pinia는 Vue.js의 상태관리 라이브러리이다. React를 배운 사람들이면 익숙한 Zustand와 거의 같은 것이라고 할 수 있는데, Zustand와는 달리 공식적으로 개발된 라이브러리라고 할 수 있다.
Pinia란

본래 Vue2까지만 하여도 Pinia가 아니라 Vuex를 사용했지만, 그 복잡함이나 이것저것 때문에 Pinia가 탄생하게 되었다. 특히 그 차이는 Mutations라고 할 수 있는데, 동기/비동기로 나누어 딱딱하게 규칙을 지키던 Vuex와 달리 규칙을 지키기보다는 간편함과 직관성을 추구하게 된 것이다. 상태 관리 라이브러리가 추구하는 것을 생각해보면 이쪽이 더 개발자에게는 편한 방법이었을 것이다.
특히 TypeScript와의 궁합이 Vuex는 최악이었는데, Pinia에 들어서는 이 TS를 최우선으로 하기 때문에 매우 궁합이 좋다. Pinia는 ① 별도의 설정 없이도 툴이 직접 타입을 추론할 수 있고 ② 일반 JS 문법과 구조에서 크게 차이가 없다는 부분 덕분에 기존 Vuex보다 훨씬 더 간단하게 개발할 수 있고, 보일러플레이트 코드가 획기적으로 줄어들어, 직관적이고 간단할 뿐 아니라 코드 자체의 효율도 높였다고 할 수 있다.
상태 관리 라이브러리로서의 장점 또한 당연히 있는데, 이에 대해서 한 번도 기술한 적이 없어 정리 겸 기술해본다. 컴포넌트 기반 아키텍처에서는 구조상 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달해야만 자식이 이를 인지하고 활용할 수 있다. 이는 명확한 데이터 흐름을 보장한다는 장점도 있지만, 전역으로 관리해야 하는 정보(대표적으로 사용자 정보, 인증 토큰 등)마저도 하위 컴포넌트로 계속해서 수동 전달해야 하는 페널티를 동반한다.
이를 흔히 Props Drilling(프롭스 드릴링)이라 부르며, 전통적인 MPA와 달리 화면 전체를 컴포넌트 단위로 쪼개어 렌더링하는 SPA 환경에서 특히 도드라지는 고질적인 약점 중 하나다. 전역 상태 관리 라이브러리는 바로 이러한 구조적 한계를 극복하고, 어떤 컴포넌트에서든 필요한 전역 데이터에 곧바로 접근할 수 있도록 돕는 핵심 도구라고 할 수 있다.
그래서 상태 관리 라이브러리를 통하여 개발자는 두 가지 선택을 할 수 있는데, 하나는 Store를 사용하여 자식에서도 Store를 불러오는 방법과, 다른 하나는 부모를 통해 props로서 정보를 받는 방법을 선택할 수 있다. 첫째는 하위 컴포넌트에서 전역 스토어(Store)를 직접 불러와 데이터를 즉시 사용하는 방법이다. props 전달 과정을 완전히 생략할 수 있어 개발 속도가 빠르고 직관적이라는 장점이 있다.
둘째는 여전히 부모 컴포넌트가 스토어에서 데이터를 꺼낸 뒤, 자식 컴포넌트에게는 props로 정보를 전달하는 방법이다. 이 방식은 자식 컴포넌트가 특정 스토어에 종속되지 않도록 만들어 주므로, 컴포넌트의 독립성과 재사용성(Reusability)을 극대화하고 싶을 때 유용하다.
Pinia를 사용하려면 `yarn add pinia`를 사용하거나, `npm install pinia`를 사용하면 된다.
Store, State, Getter, Action
Pinia의 특징은 바로 Store, State, Getter, Action 이라는 네 가지로 나뉜다는 점이다. 간단하게 넷을 정의하면, ① 상태 보관소 ② 상태 데이터 ③ 상태 데이터 가공(혹은 그대로) ④ 상태 변환 이라고 할 수 있다. 이렇게 이야기하면 잘 안 와닿을 텐데, 예제를 들어보면 다음과 같다.
여기 파인애플 전문점이 있다. 이 전문점 자체가 바로 Store이며, 가게에 쌓여 있는 신선한 원재료인 파인애플이 State(상태 데이터)가 된다. 손님이 오기 전 파인애플들은 모두 처음 입고된 그대로의 '초기 상태'를 유지한다.
가게 주인은 메뉴판에 파인애플을 그대로 팔기도 하지만, 미리 주스로 만들어 냉장고에 넣어두기도 한다. 이를 Getter라고 한다. 원본(파인애플)을 기반으로 가공된 값이며, 손님이 찾을 때 냉장고에서 바로 꺼내 주므로 매번 새로 갈 필요가 없다(자동 캐싱).
반면, 손님이 와서 '파인애플 슬라이스를 얹은 특별한 주스'를 주문하면 주인은 믹서기를 돌려 새로 주스를 만들기 시작한다. 이처럼 주문(이벤트)에 따라 상태를 동적으로 변화시키고 조리하는 과정을 Action이라고 할 수 있다.
따라서 개발자는 단순히 state만 사용하여도 좋고, Getter를 통해 State를 가공해서 사용해도 좋고, Action을 사용해서 아예 State를 완전히 동적으로 변화시켜도 좋다. 모든 것을 할 필요는 없으며, 실제로도 모든 구성요소를 사용하는 경우는 적다고 할 수 있다.
이에 따라 간단하게 이 모든 요소를 나타내면 다음과 같다 할 수 있다.
Options Store
import { defineStore } from 'pinia'
export const usePineappleStore = defineStore('pineapple', {
// 1. State (생과일 파인애플 원재료)
state: () => ({
pineappleCount: 10
}),
// 2. Getter (메뉴판의 파인애플 주스 - 자동 캐싱)
getters: {
hasPineappleJuice: (state) => state.pineappleCount >= 3
},
// 3. Action (주문을 받아 새로 믹서기를 돌리는 행위)
actions: {
makeSpecialJuice(orderAmount) {
if (this.pineappleCount >= orderAmount) {
this.pineappleCount -= orderAmount
console.log(`파인애플 ${orderAmount}개로 주스 조리 완료!`)
} else {
console.log('재료가 부족합니다!')
}
}
}
})
이 버전이 내가 배운 버전이며, 최근은 아래와 같은 setup Store 버전을 자주 사용한다.
Setup Store
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const usePineappleStore = defineStore('pineapple', () => {
// 1. State (생과일 파인애플 원재료)
const pineappleCount = ref(10)
// 2. Getter (메뉴판의 파인애플 주스 - 자동 캐싱)
const hasPineappleJuice = computed(() => pineappleCount.value >= 3)
// 3. Action (주문을 받아 새로 믹서기를 돌리는 행위)
function makeSpecialJuice(orderAmount) { // 👈 : number 타입을 제거한 순수 JS
if (pineappleCount.value >= orderAmount) {
pineappleCount.value -= orderAmount
console.log(`파인애플 ${orderAmount}개로 주스 조리 완료!`)
} else {
console.log('재료가 부족합니다!')
}
}
// 컴포넌트에서 쓸 수 있도록 외부로 공개하기
return { pineappleCount, hasPineappleJuice, makeSpecialJuice }
})
실습은 user와 feed를 이렇게 Pinia를 사용하여 구현하는 방식이다. 위에 options 상태로 구현하였는데, 조건은 feedData의 경우 initial Data의 추가와 UserData의 경우 getter를 통하여 name을 가져와야하는 형식이었다.
import {defineStore} from "pinia";
export const userStore = defineStore('userStore', {
state: () => ({
user: {
id: 1,
name: '김유저'
}
}),
getters: {
getUserName: (state) => state.user.name
}
})
import {defineStore} from "pinia";
export const FeedStore = defineStore('feedStore', {
state: () => ({
feedData: [],
testData: [{(내부에 feed data 삽입)}]
}),
actions: {
initFeed() {
this.feedData = this.testData
},
}
})
이런 식으로 처리하면 된다.
'부트챌린지 > 멋쟁이사자처럼:트위터 클론코딩' 카테고리의 다른 글
| [멋쟁이사자처럼] 부트챌린지: 11시간만에 트위터만들기#11 - Nest.js CLI로 시작하기 (0) | 2026.07.22 |
|---|---|
| [멋쟁이사자처럼] 부트챌린지: 11시간만에 트위터만들기 - Yarn 라이브러리 (0) | 2026.07.21 |
| [멋쟁이사자처럼] 부트챌린지: 11시간만에 트위터만들기 - 컴포넌트 생명주기 (0) | 2026.07.15 |
| [멋쟁이사자처럼] 부트챌린지: 11시간만에 트위터만들기 - v-if, v-for (0) | 2026.07.14 |
| [멋쟁이사자처럼] 부트챌린지: 11시간만에 트위터만들기 - computed, watch, methods (0) | 2026.07.13 |
