본문 바로가기
IT/Pinia

Pinia - State ( Option Store )

by 2T1 2023. 1. 2.
이 글은 Vue 3의 공식 Store | Pinia 를 보고 정리한 글입니다.
더 자세한 내용을 원하시면 공식 문서를 참고하세요.

 

 

Pinia에서 상태는 초기 상태를 반환하는 함수로 정의됩니다. 이를 통해 Pinia는 Server측과 Client측 모두에서 작동할 수 있습니다.

mport { defineStore } from 'pinia'

export const useStore = defineStore('storeId', {
  // 화살표 함수는 전체 유형 유추을 위해 권장됨. 
  state: () => {
    return {
      // 이 모든 속성은 자동으로 유형이 유추됨.
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})
Vue 2를 사용하는 경우, state에서 생성한 데이터는 Vue의 인스턴스의 data와 동일한 규칙을 가집니다. 즉, 상태 객체는 일반 객체여야 하며, 새 속성을 추가할 때 Vue.set()을 호출해야합니다.

참조: Vue#data.

 


 

state에 접근

store 인스턴스로 상태에 접근하여 상태를 직접 읽고 쓸 수 있습니다 :

const store = useStore()
store.count++

만약 state()에 상태를 정의해두지 않았다면, 새 상태 속성을 추가하거나 수정할 수 없습니다.


 

상태 재설정

const store = useStore()
store.$reset()

 

 

옵션 API와 사용

import { defindeStore } from 'pinia'

export const useCounterStore = defineStore('counter',{
  state: () => ({
    count: 0,
  })
})

 

 

compostion API사용하지 않고 computed, methods , ... 를 사용하는 경우, mapState() 헬퍼를 사용하여 상태 속성을 읽기 전용 계산된 속성으로 mapping 할 수 있습니다. :

import { mapState } from 'pinia'
import { useCounterStore } from '../store/counter'

export default {
  computed : {
  // 컴포넌트 내부에서 `this.coutner`로 접근 할 수 있게 함.
  // `store.count`로 읽는 것과 동일
  ...mapState(useCounterStore, ['counter']),
  
  // 위와 같지만 `this.myOwnName`으로 등록
  ...mapState(useCounterStore,{
    myOwnName: 'counter',
    
    // store에 접근하는 함수를 작성할 수도 있음
    double: store => store.count * 2,
    
    // `this`에 접근할 수 있지만, 올바르게 입력되지 않음...
    magicValue(store){
      return store.someGetter + this.count + this.double
    }
  })
  }
}

 

 

수정 가능한 상태

이러한 상태 속성을 쓸 수 있도록 하려면(ex: form 형식), mapWritableState()를 사용해야 합니다.

mapState()처럼 함수를 전달할 수 없습니다. :

import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counter'

export default {
  computed: {
    // 컴포넌트 내부에서 `this.count`로 접근할 수 있게 하고,
    // `this.count++`와 같이 수정도 허용함.
    // `store.count`에서 읽는 것과 동일.
    ...mapWritableState(useCounterStore, ['count']),
    
    // 위와 같지만 `this.myOwnName`으로 등록.
    ...mapWritableState(useCounterStore, {
      myOwnName: 'count',
    }),
  },
}
배열 전체를 cartItems = []처럼 바꾸지 않는 한, 배열 컬렉션은 mapWritableState()가 필요하지 않습니다. mapState()를 사용하면 컬렉션에서 메서드를 호출할 수 있습니다.

 

 


 

상태 변경하기

store.count++ 로 스토어를 직접 변경하는 방법 외에도, $patch 메소드를 호출하여 변경할 수 있습니다. 이 방법을 사용하여 state 객체의 일부분을 동시에 변경할 수 있습니다. :

store.$patch({
  count: store.count + 1, 
  age : 120, 
  name : 'DIO'
})

 

 

일부 mutation은 이러한 문법으로 적용하기가 힘들거나 cost가 많이 듭니다.

컬렉션을 수정(예: 배열에서 요소를 푸시, 제거, 스플라이스)하려면, 새 컬렉션을 만들어야 합니다.
이 때문에 $patch 메소드는 패치 객체로 적용하기 어려운 이러한 종류의 mutations를 그룹화하는 함수도 허용합니다:

store.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

여기서 주요 차이점은 $patch()를 사용하여 devtools에서 여러 변경 사항을 하나의 항목으로 그룹화할 수 있다는 것입니다.

state$patch()에 대한 직접적인 변경 사항은 모두 devtools에 나타나며, 시간 추적이 가능합니다. (아직 Vue 3에는 없음).

 

 

 

 


 

state 교체하기

반응성을 깨뜨릴 수 있으므로 스토어의 상태를 정확히 교체할 수 없지만 패치하여 사용할 수 있습니다.

// 실제로 `$state`를 교체하지 않음
store.$state = { count: 24 }

// 아래와 같이 내부적으로 `$patch()` 를 호출함 : 
store.$patch({ count : 24 })

 

 

또한 어플리케이션의 초기상태를 설정하여 피니아 인스턴스의 state를 변경할 수 있습니다. 이것은 하이드레이션을 위한 SSR 중에 사용됩니다.

pinia.state.value = {}

 

 

 

Q. Pattern for updating Pinia state ?

A. https://github.com/vuejs/pinia/discussions/1264

 

Best practice for updating Pinia state? · Discussion #1264 · vuejs/pinia

I'm wondering if there is a best practice, or convention for updating state in Pinia. Let's say you have some state like this: export const useExampleStore = defineStore('ExampleStore&#...

github.com

 

 

Q. Can you do something like this with Pinia?

const exampleStore = useExampleStore() 
const { isLoaded } = storeToRef(exampleStore) 
isLoaded.value = true

Or is this a bad practice and I should use action or $patch?

A . Yes, you can. 

 

트위터에서 즐기는 Eduardo.𝚟𝚞𝚎

“@jswriter_ @vuejs Yes, you can”

twitter.com

 


 

상태 구독하기

Vuex의 subscribe 메소드와 마찬가지로, 스토어의 $subscribe() 메소드를 통해 상태의 변경 사항을 감시할 수 있습니다.

일반 watch()보다 $subscribe() 사용시 장점은 구독이 여러 패치(예: 위에서 언급한 것처럼, $patch에 함수를 전달하고, 함수 내부에서 여러번의 패치가 실행됨) 이후에 한 번만 트리거된다는 것입니다.

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  
  // `cartStore.$id`와 동일.
  mutation.storeId // 'cart'
  
  // `mutation.type === 'patch object'`에서만 사용 가능.
  mutation.payload // cartStore.$patch()에 전달된 패치 객체


  // 변경될 때마다 전체 상태를 로컬 스토리지에 유지
  localStorage.setItem('cart', JSON.stringify(state))
})

 

 

기본적으로 상태 구독은 컴포넌트에 추가된(스토어가 컴포넌트의 setup() 내부에 있는) 경우에 바인딩됩니다. 따라서 컴포넌트가 마운트 해제되면 자동으로 제거됩니다. 컴포넌트가 마운트 해제된 후에도 이를 유지하려면, 두 번째 인수로 현재 컴포넌트에서 상태 구독을 분리하는
{ detached: true }를 전달합니다:

export default {
  setup() {
    const someStore = useSomeStore()

    // 이 구독은 컴포넌트가 마운트 해제된 후에도 유지됨.
    someStore.$subscribe(callback, { detached: true })

    // ...
  },
}

 

 

Pinia 인스턴스에서 전체 상태를 감시할 수 있습니다.
watch(
  pinia.state,
  (state) => {
    // 변경될 때마다 전체 상태를 로컬 스토리지에 유지
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

'IT > Pinia' 카테고리의 다른 글

Vue3 ) script setup  (0) 2023.03.28
Pinia - Action ( Option Store )  (0) 2023.01.02
Pinia - Getter ( Option Store )  (0) 2023.01.02
Pinia  (0) 2022.12.30