Pinia의 좀비 상태: 이미 겪고 있을지도 모르는 숨은 버그
Source: Dev.to
좀비 상태란?
- 페이지, 사용자 흐름, 완료된 폼, 혹은 끝난 비동기 요청이 끝난 뒤에도 남아 있는 상태.
- 여전히 다음을 할 수 있다:
- 입력값을 미리 채우기
- 검증 트리거
- UI 결정에 영향 주기
- “무작위” 버그 발생
Pinia 스토어는 기본적으로 전역, 장수 싱글톤이기 때문에, 페이지 이동만으로는 스토어 데이터가 초기화되지 않는다. 명시적으로 리셋하지 않으면, 더 이상 의미가 없어도 상태가 메모리에 남아 있다.
아주 흔한 예시
// useWizardStore.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useWizardStore = defineStore('wizard', () => {
const step = ref(2)
const email = ref('old@email.com')
return { step, email }
})
- 사용자가 마법사를 연다.
- 마법사는 2단계에서 시작하고 이전 이메일을 보여준다.
- 검증이 예상치 못하게 실패한다.
스토어가 절대 리셋되지 않아 좀비 상태가 생성된다.
비동기와 관련된 좀비 상태
async function loadUser() {
user.value = await fetchUser()
}
비동기 응답이 컴포넌트가 언마운트된 뒤 혹은 컨텍스트가 바뀐 뒤에 도착하면, 오래된 데이터가 현재 상태를 덮어써서 같은 문제가 발생한다.
주의해야 할 증상
- 채워져서는 안 되는 폼이 이미 채워져 있음
- 첫 렌더링 시 검증 오류 발생
- 네비게이션(뒤로/앞으로) 후 잘못된 데이터 표시
- 네비게이션 후 UI가 다르게 동작
- 페이지 새로고침 시 사라지는 버그
이러한 버그는 현재 상태가 아니라 네비게이션 히스토리에 의존하기 때문에 디버깅이 어렵다.
좀비 상태 방지 방법
1. 명시적인 리셋 추가
// useWizardStore.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useWizardStore = defineStore('wizard', () => {
const step = ref(1)
const email = ref('')
function reset() {
step.value = 1
email.value = ''
}
return { step, email, reset }
})
import { onUnmounted } from 'vue'
import { useWizardStore } from '@/stores/useWizardStore'
const store = useWizardStore()
onUnmounted(() => store.reset())
Pinia가 자동으로 상태를 리셋해 주지는 않는다—직접 의도적으로 해야 한다.
2. 컨텍스트 변경 시 리셋
import { watch } from 'vue'
import { useRoute } from 'vue-router'
import { useWizardStore } from '@/stores/useWizardStore'
const route = useRoute()
const store = useWizardStore()
watch(
() => route.params.id,
() => store.reset()
)
이렇게 하면 오래된 데이터가 새로운 컨텍스트에 섞이는 것을 방지한다.
3. 비동기 요청 보호
let requestId = 0
async function load() {
const id = ++requestId
const data = await fetchData()
if (id !== requestId) return // 오래된 응답 무시
state.value = data
}
늦게 도착한 응답은 무시하고 유효한 상태를 덮어쓰지 않는다.
4. UI 상태와 도메인 상태 분리
- 스토어에 적합한 후보: 사용자 데이터, 인증 정보, 공유 도메인 엔티티.
- 스토어에 부적합한 후보: 단명 UI 상태(예: 모달 표시 여부, 일시적인 폼 필드). 이런 것은 컴포넌트에 가깝게 두자.
오래된 상태의 종류
| Type | Meaning |
|---|---|
| Stale state | 오래됐지만 여전히 유효 |
| Zombie state | 무효하지만 여전히 활성 |
결론
좀비 상태는 보기에는 정상적이지만 실제로는 그렇지 않다. 상태가 소유자를 초과해 살아남으면 좀비가 된다. 이 문제는 Pinia에만 국한된 것이 아니라 상태‑수명 주기 문제이다.
Pinia는 강력한 도구를 제공하지만, 그만큼 책임도 따른다:
- 소유권 정의
- 수명 관리
- 의도적인 리셋
이러한 관행을 적용하면 다음과 같은 애플리케이션을 만들 수 있다:
- 이해하기 쉬움
- 디버깅이 쉬움
- 사용자에게 더 예측 가능함
이 글이 한 명이라도 어려운 프로덕션 버그를 피하는 데 도움이 된다면 공유할 가치가 있다.