Vue(+vuex)로 만든 프로젝트를 리팩토링 하던 중에 났던 에러이다.
이 에러가 나니까 const store = useStore()로 store.state에 접근하는 모든 부분이 store가 undefined로 떨어지면서 에러가 났다.
단순히 useStore를 컴포넌트 또는 컴포저블(react의 커스텀 훅과 같은 역할)의 최상단에서 쓰면 해결됐지만 이유가 궁금해졌다.
useState최상단에 써야 한다는 규칙을 보니, React의 useState가 떠올랐다.
useState를 최상단에서 사용해야 하는 이유React는 컴포넌트 내부에서 호출되는 훅들의 순서를 기억한다.
각 훅은 컴포넌트가 처음 렌더링될 때 순서대로 호출되며, 이후 리렌더링 시에도 동일한 순서로 호출된다.
만약 useState 훅이 조건문이나 반복문 내부에서 호출된다면, 컴포넌트가 리렌더링될 때마다 훅의 호출 순서가 달라질 수 있다.
예를 들어, 특정 조건이 만족될 때만 useState 훅이 호출된다면, 조건이 만족되지 않는 리렌더링에서는 해당 훅이 호출되지 않아 훅의 순서가 밀린다.
결론적으로 이야기하면, 다른 이유 때문이다.
"inject() can only be used inside setup() or functional components." 경고는 Vue의 컴포넌트 라이프 사이클과 관련 있다.
아래와 같은 상황에서 발생한다.
<script setup>
import { useStore } from "vuex";
const doAction = () => {
const store = useStore();
console.log(store.state.count);
};
</script>
<template>
<div @click="doAction">action</div>
</template>
이렇게 고치면 에러는 해결된다.
<script setup>
import { useStore } from "vuex";
const store = useStore();
const doAction = () => {
console.log(store.state.count);
};
</script>
<template>
<div @click="doAction">action</div>
</template>
vuex는 Vue 컴포넌트 간의 상태 관리를 중앙 집중화하는 라이브러리이다.
useStore는 내부적으로 Vue의 inject(props drilling을 해결 - https://ko.vuejs.org/guide/components/provide-inject) 함수를 사용한다.
inject 함수는 Vue의 의존성 주입 시스템을 통해 부모 컴포넌트에서 제공한 값을 가져오는 데 사용되며, 이 의존성 주입은 setup 함수 또는 함수형 컴포넌트 내에서만 사용할 수 있도록 설계되었다.
Vue의 의존성 주입 시스템은 컴포넌트 트리의 특정 컨텍스트 내에서 작동한다.
setup 함수 내부는 의존성 주입 컨텍스트 내에 있으므로 inject 함수를 안전하게 사용할 수 있습다.
그렇지만 컴포넌트의 컴포넌트의 다른 부분(예: methods 옵션)에서는 의존성 주입 컨텍스트가 보장되지 않으므로 inject를 직접 사용할 수 없다.
cf) Vue 컴포넌트 라이프사이클
1. Creation (생성)
- setup: 컴포넌트 인스턴스가 생성되기 직전에 호출. reactive state, computed properties, methods 등을 설정. inject 함수는 setup 함수 내에서 호출되어야 함.
- onBeforeMount
2. Mounting (마운트)
- render: 컴포넌트의 가상 DOM을 생성.
- onMounted
3. Updating (업데이트)
- onBeforeUpdate
- render: 컴포넌트의 가상 DOM을 다시 생성.
- onUpdated
4. Unmounting (언마운트)
- onBeforeUnmount
- onUnmounted
ref도 갑자기 궁금해졌다.ref를 최상단에서 사용하지 않으면, 반응성을 잃을까? 예상치 못한 동작을 할 수 있을까?
<script setup>
import { ref } from "vue";
let zz = undefined;
const setZZ = () => {
if (!zz) {
zz = ref("");
}
zz.value = zz.value += "z";
};
</script>
<template>
<button type="button" @click="setZZ">+</button>
<div>{{ zz ?? "no data" }}</div>
</template>
이 글에 적혀 있던 의식의 흐름을 따르면 바로 위 코드는 정상적으로 동작하지 않을 것 같지만, 의외로 아무 문제 없이 동작한다(굳이 이처럼 코드를 같이 짤 필요도 전혀 없지만 말이다).
그 이유는 ref는 useState처럼 클로저와 배열 개념을 통해 메모리에 저장하고 있는 것도 아니며, Vue 컴포넌트의 라이프사이클에 의존하고 있는 것도 아니기 때문이다.
ref는 Proxy 개념을 사용하여 .value 속성에 getter 및 setter로 정의하여 값에 대한 접근 및 변경을 추적하여 반응성 데이터를 유지한다.
구현 방식을 자세히 알고 싶다면, 공식 깃헙을 참고.
~inject 에러에서 멀리도 돌아왔지만 확실히 알고 가니까 좋은 거겠지?~