스토어란 특정 컴포넌트에 바인딩되지 않은 상태 또는 처리해야 할 일의 로직을 가지는 것이다. 즉 모두가 사용하는 전역 상태를 호스팅 한다. 따라서 스토어에 저장된 정보는 모든 페이지나 컴포넌트에서 공유될 수 있는 것이다.
이렇게 이야기 하면 많은 경우 스토어를 만능 저장소로 생각해서 모든 데이터를 빨아들이는 블랙홀로 생각할 수 있는데 당연히 지양해야 할 태도다. 우리가 프로그래밍 하다 보면 전역 변수의 사용이 많지 않은 것과 같은 이유이다.
스토어에 저장할만한 정보는 여러 페이지에서 보여지는 사용자의 로그인 정보나 다단계를 거쳐서 구성되는 복잡한 데이터 등이다. 상태 관리를 한다고 해서Pinia를 데이터베이스 처럼 사용해서는 곤란하다. pinia는마치 backend의 세션 정도로 생각하면 좋겠다. 게시판을 작성한다고 했을 때 세션에 게시물 정보를 저장하지는 않는다.
다만 여러 페이지에서 공유해야 할 사용자 정보나 검색 조건을 유지해야 할 때 검색 조건, 페이지 번호 정도가 pinia에 관리해볼 만한 내용이다. 또한 단순히 부모 자식 간의 props/emits를 store에서 관리할 것인지도 고민할 꺼리다. props와 emits는 직관적으로 파악이 가능하지만 store는 분석이 필요하다. 또한 depth가 깊어지면 provide/inject도 훌륭한 도구이다.
이 3가지 경우(props/emits, provide/inject, store)를 칼같이 나눠서 언제 누구를 써야한다라고 정의하기는 너무 어렵다. pinia에서도 왜 pinia를 써야하는지를 언급하면서 비지니스 로직적인 차이를 언급하지는 않고 부가적인 장점(devtool 연동등)을 이야기 할 정도다. 상황에 맞춰서 판단하자.
getters: state의 값을 filtering 하거나 readonly로 조회하기 위한 함수로 computed로 작성
actions: business logic에 의해 state를 변경하기 위한 함수
이 3가지를 구성하는 것이 defineStore의 두 번째 파라미터인데 option 객체 또는 setup function을 사용할 수 있다. 어치피 동작은 동일하기 때문에 선호하는 vue 작성 스타일에 따라 작성해주면 된다. 아래 그림의 좌측이 setup, 우측이 options 객체 형태이다.
우리는 setup으로 간다!
options 방식은 state, getters, actions 가 명확히 구분되어있다. 이에 대응하는 setup 함수의 요소를 살펴보면 state는 ref 또는 reactive를 통한 반응형 객체, getters는 computed, actions는 일반 함수가 된다. (options가 명확해보이지만 하다보면 setup이 편하다는..)
참고로 vuex에서는 mutations(변경작업)와 actions(비동기작업)로 분리되었었지만 pinia에서는 둘이 합쳐졌다.
사용해보기
store를 컴포넌트에서 사용할 때는 저장소 js를 import 한 후 defineStore로 작성한 함수 객체를 이용한다.
import { useCounterStore } from "@/stores/counter.js"
const counterStore = useCounterStore() // proxy 객체
console.log(`[CounterView.vue] counterStore:`, counterStore)
// destructuring을 하련느 경우 storeToRefs 활용
import { storeToRefs } from "pinia";
// ref, computed 처럼 XXRefImpl 타입의 객체들(단순 destrecturing은 반응성이 소멸됨)
const { count, doubleCount } = storeToRefs(counterStore)
// 그 외의 객체들(storeToRefs에서는 undefined 처리됨)
const { increment } = counterStore;
store에서 return 된 객체는 reactive로 감싸서 전달된다. 따라서 값에 접근할 때는 초기 타입(ref, reactive 등)에 상관 없이 .value를 사용하지 않고 접근한다.
만약 매번 변수를 통해서 접근하는 것이 부담스럽다면 destructuring 해서 사용할 수 있는데 그냥 destructuring 하면 XXRefImpl 반응성을 잃어버리기 때문에 주의가 필요하다. XXRefImpl을 반응성을 유지한 채 destructuring 처리할 때에는 pinia에서 제공하는 storeToRefs 함수를 이용하면 된다. storeToRefs는 vue의 toRefs와 유사한데 비 반응성 데이터나 일반 함수는 애초에 제외하는 등 차이가 있다. (훨씬 깔끔하게 벗겨진다고 해야하나..)
개인적으로 굳이 destructuring 해야할 필요가 있나 싶다. 반응성을 위해서 다시 변환해야 하고, 여러개의 store를 사용한다면 이름 충돌을 대비하여 namespace를 지정해야할텐데..