pinia state watch console issues

Bora Im·2024년 7월 3일

Vue 3 + Pinia 환경에서 store의 state값 watch 실습

// store
state: () => ({
  testNumber: 0,
  testArray: [],
}),

// component
watch(
  () => testArray,
  (newValue, oldValue) => {
    console.log('watch', { newValue, oldValue });
  }
);

testArray.value.push('foo');

숫자값인 testNumber가 0에서 1로 변경되면
test watch {newValue: 1, oldValue: 0}를 의도대로 띄운다.
string 배열인 testArray 가 변경되었을 때는 콘솔 로그를 띄우지 않는다. 💥
💡동일한 객체를 참조하고 있어 감지가 안 되는것 같다.
deep 옵션을 통해 깊은 감시를 해보니 콘솔 로그를 띄운다.
하지만, newValue: ['foo'], oldValue: ['foo'] 두 값이 동일하다.. 💥

공부해야지
Deep Watchers | Vue.js
깊은 감시자 | Vue.js
비슷한 예시가 나와있다.

반응형 객체에서 직접 watch()를 호출하면 암시적으로 깊은 감시자가 생성됩니다.
-콜백은 모든 중첩된 변경에 대해 트리거됩니다:

const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 중첩된 속성 변경 시 실행됩니다
  // 참고: `newValue`는 여기서 `oldValue`와 동일합니다.
  // 두 값 모두 동일한 객체를 가리키기 때문입니다!
})

obj.count++

반응형 객체를 반환하는 getter와는 차이가 있습니다.
-후자의 경우, getter가 다른 객체를 반환할 때만 콜백이 실행됩니다.
그러나, deep 옵션을 명시적으로 사용하여 두 번째 경우를 깊은 감시자로 만들 수 있습니다:

watch(
  () => state.someObject,
  (newValue, oldValue) => {
    // 참고: state.someObject가 교체되지 않는 한,
    // 여기서 `newValue`는 `oldValue`와 동일합니다.
  },
  { deep: true }
)

어쨌든 deep옵션을 사용하더라도 감지는 할 수 있되 oldValue 는 못 찾아준다(?)는 소린데.
그러면 믿을 곳은 챗gpt!

상태값을 변경하기 전에 참조를 복사하는 방법을 추천해준다.

JavaScript Array.slice() 얕은 복사와 Vue의 nextTick()을 이용한다.

// 배열의 얕은 복사
function shallowCopy(array) {
  return array.slice();
}

let oldArray = shallowCopy(testArray); //1️⃣

watch(
  testArray,
  async (newValue) => {
    await nextTick(); //2️⃣Vue의 반응성 시스템이 업데이트될 때까지 기다립니다.
    console.log('watch:', { newValue, oldValue: oldArray });
    oldArray = shallowCopy(newValue); //3️⃣oldArray를 업데이트합니다.
  },
  { deep: true }
);

그랬더니 콘솔 로그로 원하는 값을 반환 했다!
newValue: Proxy(Array) {0: 'foo'} oldValue: []

지피티의 부가 설명은 이렇다.

1️⃣ shallowCopy 함수는 배열을 얕게 복사하여 새로운 배열을 반환합니다.
이를 통해 oldValuenewValue와 동일한 참조로 변경되는 것을 방지합니다.
2️⃣ nextTick을 사용하여 Vue의 반응성 시스템이 업데이트된 후에 watch의 콜백 함수가 실행되도록 합니다. 이를 통해 상태 변경이 완전히 적용된 후에 로그를 출력할 수 있습니다.
3️⃣ watch의 콜백 함수 내에서 oldArray를 새로운 값으로 업데이트하여
다음 변경 감지 시 정확한 이전 값을 참조할 수 있게 합니다.

깊이 들어갈수록 너무 어렵다..
newValue, oldValue 의 형식이 어째 약간 다르기도 하고 (ㅍ,푸록시 ..?
서치하면 할수록 ref reactive의 개념이 잘 안 잡혀있는 느낌이다.
결론: 실습하면서 계속 조금씩 더 찾아봐야겠다.

🔗reactive와 ref
🔗ref, reactive, toRef, toRefs, 자바스크립트 프록시


일단 현상 기록.

// pinia store
export const useCartStore = defineStore('cart', {
  state: () => ({
    rawItems: [],
  }),
  getters: {
    items: (state) =>
      state.rawItems.reduce((items, item) => {
        const existingItem = items.find((it) => it.name === item);

        if (!existingItem) {
          items.push({ name: item, amount: 1 });
        } else {
          existingItem.amount++;
        }

        return items;
      }, []),
  },
});

staterawItems
rawItems을 가공한 getters 값인 items가 있을 때,

const { rawItems, items } = storeToRefs(cart);

onMounted(() => {
  console.log(rawItems); // ObjectRefImpl {}
  console.log(rawItems.value); // Proxy(Array) {0: 'foo'}
  console.log(items); // ComputedRefImpl {}
  console.log(items.value); // [{name: "foo", amount: 1}]
  // 🍍 "cart" store installed 🆕
});

onMounted(() => {
  nextTick(() => {
    // 🍍 "cart" store installed 🆕
    console.log(rawItems); // ObjectRefImpl {}
    console.log(rawItems.value); // Proxy(Array) {0: 'foo'}
    console.log(items); // ComputedRefImpl {}
    console.log(items.value); // [{name: "foo", amount: 1}]
  });
});

0개의 댓글