대부분의 경우, Store는 state를 중심으로 이루어지며, 일반적으로 앱의 state를 정의하는 것부터 시작합니다. Pinia에서 state는 초기 state를 반환하는 함수로 정의됩니다. 이것은 Pinia가 서버와 클라이언트 측에서 모두 작동할 수 있게 합니다.
export const useStore = defineStore('storeId', {
// 완전한 타입 추론을 위해 화살표 함수를 사용하는 것이 좋습니다.
state: () => {
return {
// 이 모든 프로퍼티들은 자동으로 타입이 추론됩니다.
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
state를 TS와 호환되게 만들기 위해 많은 노력이 필요하지 않습니다. strict 또는 최소한 noImplicitThis를 활성화하면 Pinia가 state 타입을 자동으로 추론합니다! 하지만 몇 가지 경우에는 캐스팅으로 보조해야 합니다:
interface UserInfo {
name: string
age: number
}
export const useUserStore = defineStore('user', {
state: () => {
return {
// 최초에 비어있는 배열의 경우.
userList: [] as UserInfo[],
// 아직 로드되지 않은 데이터의 경우.
user: null as UserInfo | null,
}
},
})
원한다면, 인터페이스를 사용하여 state를 정의하고 state()의 반환 값에 타입을 지정할 수 있습니다:
interface State {
userList: UserInfo[]
user: UserInfo | null
}
interface UserInfo {
name: string
age: number
}
export const useUserStore = defineStore('user', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
기본적으로 store 인스턴스를 통해 직접 state에 접근하여 읽고 쓸 수 있습니다:
const store = useStore()
store.count++
state()에 정의하지 않은 새로운 프로퍼티를 추가할 수 없습니다. 최초에 state에 포함되어야 합니다. 예를 들어, secondCount가 state()에 정의되어 있지 않다면, store.secondCount = 2를 실행할 수 없습니다.
Option Store에서는 Store의 $reset() 메서드를 호출하여 state를 초기 값으로 재설정할 수 있습니다.
const store = useStore()
store.$reset()
내부적으로 이것은 새로운 state 객체를 생성하기 위해 state() 함수를 호출하고 현재 state를 그것으로 대체합니다.
💡Setup Store에서는 직접 $reset() 메서드를 생성해야 합니다:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0 // 초기화
}
return { count, $reset }
})
store.count++로 Store를 직접 수정하는 것 외에도 $patch 메서드를 호출할 수 있습니다. 이 메서드를 사용하면 state의 일부 객체를 패치하여 여러 변경 사항을 동시에 적용할 수 있습니다.
store.$patch({ // 객체 형태
count: store.count + 1,
age: 120,
name: 'DIO',
})
하지만 이 문법으로는 일부 변경 사항을 적용하기 어렵거나 비용이 많이 듭니다. 예를 들어, 배열에 요소를 추가하거나 제거 또는 splice 작업을 하는 등 컬렉션을 수정하려면 새 컬렉션을 생성해야 합니다. 이 때문에 $patch 메서드는 패치 객체로 적용하기 어려운 이러한 종류의 변경을 함수를 사용하여 그룹화할 수도 있습니다.
store.$patch((state) => { // 함수 형태
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
주된 차이점은 $patch()가 여러 변경 사항을 devtools에서 하나의 항목으로 그룹화할 수 있다는 점입니다. 또한, state를 직접 변경하는 것과 $patch()는 모두 devtools에 나타나며 time travel이 가능합니다 (Vue 3의 devtools에서는 아직 지원되지 않습니다).
Store의 state를 완전히 교체할 수는 없습니다. 그렇게 하면 반응성이 깨지기 때문입니다. 하지만 patch 할 수는 있습니다:
// ❌이것은 실제로 `$state`를 교체하지 않습니다.
store.$state = { count: 24 }
// 내부적으로 `$patch()`를 호출합니다:
store.$patch({ count: 24 })
pinia 인스턴스의 state를 변경하여, 전체 애플리케이션의 초기 state를 설정할 수도 있습니다. 이는 SSR State 하이드레이션에서 일반적으로 사용됩니다.
pinia.state.value = {}
Vuex의 subscribe 메서드과 유사하게, Store의 $subscribe() 메서드를 통해 state 변화를 감지할 수 있습니다. watch()를 사용하는 것보다 $subscribe()를 사용시 장점은 subscribe이 fetch 후에 한 번만 트리거된다는 점입니다 (예: 위에서 언급한 함수 방식을 사용할 때).
cartStore.$subscribe((mutation, state) => {
mutation.type // 'direct' | 'patch object' | 'patch function'
// cartStore.$id와 동일.
mutation.storeId // 'cart'
// mutation.type === 'patch object'인 경우에만 사용 가능.
mutation.payload // cartStore.$patch()에 전달된 패치 객체
// 변경될 때마다 전체 state를 로컬 저장소에 유지합니다.
localStorage.setItem('cart', JSON.stringify(state))
})
기본적으로 state 구독은 컴포넌트에 추가한 경우에 바인딩됩니다 (Store가 컴포넌트의 setup() 내부에 있는 경우). 이는 해당 컴포넌트가 마운트 해제될 때 자동으로 제거됨을 의미합니다.
컴포넌트가 마운트 해제된 후에도 구독을 유지하려면 두 번째 인수로 { detached: true }를 전달하여 state 구독을 현재 컴포넌트에서 분리하십시오:
<script setup>
const someStore = useSomeStore()
// 이 구독은 컴포넌트가 마운트 해제된 후에도 유지됩니다.
someStore.$subscribe(callback, { detached: true })
</script>
🧐 특정 상태만 감시하고자 한다면
watch()가 적합하며, 스토어의 모든 상태 변화에 대한 포괄적인 감시가 필요하다면$subscribe()를 사용할 수 있습니다.
watch()는 Vue 컴포지션 API의 기능으로, 특정 반응형 데이터나 계산된 속성을 관찰하고, 그 값이 변경될 때마다 실행됩니다. Pinia 상태도 반응형이므로watch()를 사용할 수 있습니다.
- 주로 특정 상태나 계산된 속성의 변화를 감지하고자 할 때 사용합니다.
- 깊은 감시를 설정할 수 있어, 객체 내부의 속성 변화도 감지 가능합니다.
- 스토어 외의 다른 반응형 데이터에도 사용할 수 있습니다.
$subscribe()는 Pinia에서 제공하는 메서드로, 스토어의 상태 변화에 반응할 수 있습니다. 주로 스토어의 상태 변화를 기록하거나 특정 로직을 실행할 때 사용됩니다.
- Pinia 스토어에 특화된 구독 방식입니다.
- 상태 변경의 유형(예: direct, patch object, patch function)에 대한 추가 정보를 제공합니다.
- 주로 상태 변경을 로깅하거나, 특정 변경에 대한 처리 로직을 작성할 때 유용합니다.