상태 머신(XState)
상태 머신은 유한한 상태들과 상태 간 전환 규칙을 정의하여, "불가능한 상태를 불가능하게" 만드는 모델링 기법입니다.
면접에서 "복잡한 UI 상태를 어떻게 관리하나요?"라고 물으면, boolean 플래그의 조합 폭발 문제를 설명하고 상태 머신이 이를 어떻게 해결하는지 답할 수 있으면 좋습니다.
boolean 플래그의 문제
// 데이터 로딩 상태를 boolean으로 관리하면...
const isLoading = ref(false)
const isError = ref(false)
const isSuccess = ref(false)
const isEmpty = ref(false)
// 문제: isLoading && isError가 동시에 true일 수 있음!
// 2^4 = 16가지 조합 중 유효한 것은 4가지뿐
상태 머신으로 해결
// machines/fetchMachine.ts
import { createMachine, assign } from 'xstate'
interface FetchContext {
data: any | null
error: string | null
}
type FetchEvent =
| { type: 'FETCH' }
| { type: 'RESOLVE'; data: any }
| { type: 'REJECT'; error: string }
| { type: 'RETRY' }
export const fetchMachine = createMachine({
id: 'fetch',
initial: 'idle',
context: { data: null, error: null } as FetchContext,
states: {
idle: {
on: { FETCH: 'loading' }
},
loading: {
on: {
RESOLVE: {
target: 'success',
actions: assign({ data: (_, event) => event.data, error: null })
},
REJECT: {
target: 'failure',
actions: assign({ error: (_, event) => event.error, data: null })
}
}
},
success: {
on: { FETCH: 'loading' }
},
failure: {
on: {
RETRY: 'loading',
FETCH: 'loading'
}
}
}
})
상태 전환 다이어그램:
idle → (FETCH) → loading
loading → (RESOLVE) → success
loading → (REJECT) → failure
success → (FETCH) → loading
failure → (RETRY) → loading
불가능한 상태는 정의조차 되지 않음!
Vue에서 XState 사용
<script setup lang="ts">
import { useMachine } from '@xstate/vue'
import { fetchMachine } from '@/machines/fetchMachine'
const { state, send } = useMachine(fetchMachine)
const fetchData = async () => {
send('FETCH')
try {
const res = await fetch('/api/data')
const data = await res.json()
send({ type: 'RESOLVE', data })
} catch (e) {
send({ type: 'REJECT', error: (e as Error).message })
}
}
</script>
<template>
<div>
<!-- 현재 상태에 따라 UI 표시 -->
<div v-if="state.matches('idle')">
<button @click="fetchData">데이터 로드</button>
</div>
<div v-else-if="state.matches('loading')">
<p>로딩 중...</p>
</div>
<div v-else-if="state.matches('success')">
<pre>{{ state.context.data }}</pre>
<button @click="fetchData">새로고침</button>
</div>
<div v-else-if="state.matches('failure')">
<p>에러: {{ state.context.error }}</p>
<button @click="send('RETRY')">재시도</button>
</div>
</div>
</template>
상태 머신이 적합한 경우
적합:
✓ 멀티스텝 폼/위저드
✓ 인증 플로우 (로그인/로그아웃/토큰 갱신)
✓ 미디어 플레이어 (재생/일시정지/정지/버퍼링)
✓ 드래그 앤 드롭 UI
부적합:
✗ 단순한 토글 (v-if로 충분)
✗ CRUD 목록 (Pinia로 충분)
✗ 간단한 폼 (ref/reactive로 충분)
면접 팁
- "불가능한 상태를 불가능하게(Make impossible states impossible)" — 이 원칙을 설명할 수 있으면 상태 관리에 대한 깊은 이해를 보여줍니다
- 모든 상태에 상태 머신을 쓸 필요는 없습니다. 복잡한 상태 전환이 있을 때만 도입하세요
- XState의 시각화 도구(xstate.js.org/viz)를 활용하면 상태 다이어그램을 팀과 공유할 수 있습니다
요약
상태 머신은 boolean 플래그의 조합 폭발 문제를 해결하고, 유한한 상태와 명시적 전환으로 예측 가능한 UI를 만듭니다. XState의 useMachine으로 Vue에서 쉽게 사용할 수 있으며, 복잡한 UI 플로우(멀티스텝 폼, 인증, 미디어 플레이어)에 적합합니다.
댓글 로딩 중...