상태 머신은 유한한 상태들과 상태 간 전환 규칙을 정의하여, "불가능한 상태를 불가능하게" 만드는 모델링 기법입니다.

면접에서 "복잡한 UI 상태를 어떻게 관리하나요?"라고 물으면, boolean 플래그의 조합 폭발 문제를 설명하고 상태 머신이 이를 어떻게 해결하는지 답할 수 있으면 좋습니다.


boolean 플래그의 문제

TYPESCRIPT
// 데이터 로딩 상태를 boolean으로 관리하면...
const isLoading = ref(false)
const isError = ref(false)
const isSuccess = ref(false)
const isEmpty = ref(false)

// 문제: isLoading && isError가 동시에 true일 수 있음!
// 2^4 = 16가지 조합 중 유효한 것은 4가지뿐

상태 머신으로 해결

TYPESCRIPT
// 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'
      }
    }
  }
})
PLAINTEXT
상태 전환 다이어그램:

  idle → (FETCH) → loading
  loading → (RESOLVE) → success
  loading → (REJECT) → failure
  success → (FETCH) → loading
  failure → (RETRY) → loading

불가능한 상태는 정의조차 되지 않음!

Vue에서 XState 사용

VUE
<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>

상태 머신이 적합한 경우

PLAINTEXT
적합:
✓ 멀티스텝 폼/위저드
✓ 인증 플로우 (로그인/로그아웃/토큰 갱신)
✓ 미디어 플레이어 (재생/일시정지/정지/버퍼링)
✓ 드래그 앤 드롭 UI

부적합:
✗ 단순한 토글 (v-if로 충분)
✗ CRUD 목록 (Pinia로 충분)
✗ 간단한 폼 (ref/reactive로 충분)

면접 팁

  • "불가능한 상태를 불가능하게(Make impossible states impossible)" — 이 원칙을 설명할 수 있으면 상태 관리에 대한 깊은 이해를 보여줍니다
  • 모든 상태에 상태 머신을 쓸 필요는 없습니다. 복잡한 상태 전환이 있을 때만 도입하세요
  • XState의 시각화 도구(xstate.js.org/viz)를 활용하면 상태 다이어그램을 팀과 공유할 수 있습니다

요약

상태 머신은 boolean 플래그의 조합 폭발 문제를 해결하고, 유한한 상태와 명시적 전환으로 예측 가능한 UI를 만듭니다. XState의 useMachine으로 Vue에서 쉽게 사용할 수 있으며, 복잡한 UI 플로우(멀티스텝 폼, 인증, 미디어 플레이어)에 적합합니다.

댓글 로딩 중...