"isLoading && !isError && isSubmitted" — 이런 불리언 조합 지옥에서 벗어나는 방법이 상태 머신입니다.

개념 정의

상태 머신(State Machine) 은 시스템이 가질 수 있는 유한한 상태와, 상태 간 전이(transition)를 명시적으로 정의하는 모델입니다. "불가능한 상태를 불가능하게 만드는" 패턴입니다.

불리언 지옥 vs 상태 머신

JAVASCRIPT
// 불리언 조합 — 불가능한 조합이 존재
let isLoading = false;
let isError = false;
let isSuccess = false;
// isLoading && isError && isSuccess = true? 이건 말이 안 됨

// 상태 머신 — 한 번에 하나의 상태만 가능
let state = 'idle'; // 'idle' | 'loading' | 'success' | 'error'

Svelte에서 간단한 상태 머신

SVELTE
<script>
  // 수동 상태 머신 구현
  const states = {
    idle: { FETCH: 'loading' },
    loading: { SUCCESS: 'success', ERROR: 'error' },
    success: { RESET: 'idle' },
    error: { RETRY: 'loading', RESET: 'idle' },
  };

  let currentState = $state('idle');
  let data = $state(null);
  let error = $state(null);

  function send(event) {
    const nextState = states[currentState]?.[event];
    if (nextState) currentState = nextState;
  }

  async function fetchData() {
    send('FETCH');
    try {
      const res = await fetch('/api/data');
      data = await res.json();
      send('SUCCESS');
    } catch (e) {
      error = e.message;
      send('ERROR');
    }
  }
</script>

{#if currentState === 'idle'}
  <button onclick={fetchData}>데이터 불러오기</button>
{:else if currentState === 'loading'}
  <p>로딩 중...</p>
{:else if currentState === 'success'}
  <pre>{JSON.stringify(data, null, 2)}</pre>
  <button onclick={() => send('RESET')}>초기화</button>
{:else if currentState === 'error'}
  <p class="error">{error}</p>
  <button onclick={fetchData}>재시도</button>
  <button onclick={() => send('RESET')}>초기화</button>
{/if}

XState와 Svelte 통합

BASH
npm install xstate @xstate/svelte
SVELTE
<script>
  import { useMachine } from '@xstate/svelte';
  import { createMachine, assign } from 'xstate';

  const fetchMachine = createMachine({
    id: 'fetch',
    initial: 'idle',
    context: { data: null, error: null },
    states: {
      idle: {
        on: { FETCH: 'loading' }
      },
      loading: {
        invoke: {
          src: 'fetchData',
          onDone: {
            target: 'success',
            actions: assign({ data: ({ event }) => event.output }),
          },
          onError: {
            target: 'error',
            actions: assign({ error: ({ event }) => event.error.message }),
          },
        },
      },
      success: { on: { RESET: 'idle' } },
      error: { on: { RETRY: 'loading', RESET: 'idle' } },
    },
  });

  const { state, send } = useMachine(fetchMachine, {
    actors: {
      fetchData: async () => {
        const res = await fetch('/api/data');
        if (!res.ok) throw new Error('요청 실패');
        return res.json();
      }
    }
  });
</script>

{#if $state.matches('idle')}
  <button onclick={() => send({ type: 'FETCH' })}>불러오기</button>
{:else if $state.matches('loading')}
  <p>로딩 중...</p>
{:else if $state.matches('success')}
  <pre>{JSON.stringify($state.context.data, null, 2)}</pre>
{:else if $state.matches('error')}
  <p>{$state.context.error}</p>
  <button onclick={() => send({ type: 'RETRY' })}>재시도</button>
{/if}

멀티스텝 폼 상태 머신

JAVASCRIPT
const formMachine = createMachine({
  id: 'multiStepForm',
  initial: 'personalInfo',
  context: { personal: {}, address: {}, payment: {} },
  states: {
    personalInfo: {
      on: {
        NEXT: {
          target: 'addressInfo',
          actions: assign({ personal: ({ event }) => event.data }),
        },
      },
    },
    addressInfo: {
      on: {
        NEXT: {
          target: 'paymentInfo',
          actions: assign({ address: ({ event }) => event.data }),
        },
        BACK: 'personalInfo',
      },
    },
    paymentInfo: {
      on: {
        SUBMIT: 'submitting',
        BACK: 'addressInfo',
      },
    },
    submitting: {
      invoke: {
        src: 'submitForm',
        onDone: 'complete',
        onError: 'paymentInfo',
      },
    },
    complete: { type: 'final' },
  },
});

면접 포인트

  • "상태 머신을 왜 쓰나요?": 불리언 플래그 조합으로는 "불가능한 상태"(로딩 중이면서 에러인 상태)를 코드 레벨에서 방지할 수 없습니다. 상태 머신은 가능한 상태와 전이를 명시적으로 정의하여 이를 원천 차단합니다.
  • "모든 상태를 상태 머신으로 관리해야 하나요?": 아닙니다. 간단한 토글이나 단순 카운터에는 $state로 충분합니다. 비동기 플로우, 멀티스텝 프로세스, 복잡한 UI 상태에 상태 머신이 빛납니다.

정리

상태 머신은 "가능한 상태를 명시하고, 불가능한 전이를 차단하는" 패턴입니다. 복잡한 UI 플로우에서 불리언 조합의 혼란을 정리하고, 상태 다이어그램으로 시각화까지 가능합니다.

댓글 로딩 중...