DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 970,177 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Park Je Hoon
Park Je Hoon

Posted on

XState (1)

졜근 μƒνƒœ 관리 λΌμ΄λΈŒλŸ¬λ¦¬μ— λŒ€ν•œ λ¦¬μ„œμΉ˜λ₯Ό ν•˜λ‹€κ°€ κ΄€μ‹¬μžˆκ²Œ 보고있던 XStateλ₯Ό 곡뢀해보고 정리할 κ²Έ μž‘μ„±. λ‚΄μš©μ΄ λ„ˆλ¬΄ λ§Žμ„ 것 κ°™μ•„μ„œ ν‹ˆλ‚ λ•Œλ§ˆλ‹€ μ—…λ°μ΄νŠΈ ν•΄λ‚˜μ•„κ°ˆ μ˜ˆμ •μ΄λ‹€.

Xstate

XStateλŠ” FSM(Finite State Machine)κ³Ό Statechartsλ₯Ό 생성, 해석, μ‹€ν–‰ν•˜κΈ° μœ„ν•œ 라이브러리이며, ν•΄λ‹Ή machine의 ν˜ΈμΆœμ„ actor둜 κ΄€λ¦¬ν•˜κΈ° μœ„ν•œ λΌμ΄λΈŒλŸ¬λ¦¬μ΄λ‹€.

μ•Œμ•„μ•Όν•  κ°œλ…μ€ FSM, Statecharts, Actor model듀에 λŒ€ν•œ λ‚΄μš©μΈλ° 이 곳에 μ½μ–΄λ³Όλ§Œν•œ λ ˆνΌλŸ°μŠ€λ“€μ΄ μžˆλ‹€. κ³Όκ±° λŒ€ν•™κ΅μ—μ„œ 이산 μˆ˜ν•™ κ°•μ˜λ₯Ό λ“€μ—ˆμ„ λ•Œ λ§ˆμ§€λ§‰ μ±•ν„°μ˜€λ˜ FSM에 λŒ€ν•΄μ„œ μˆ˜ν•™μ˜ μ •μˆ˜λΌκ³  λ§μ”€μ£Όμ…¨λ˜ 기얡이 λ‚œλ‹€.

μœ„ν‚€μ˜ λ‚΄μš©μ„ 보자면 μœ ν•œν•œ 개수의 μƒνƒœλ₯Ό κ°€μ§ˆ 수 μžˆλŠ” μ˜€ν† λ§ˆνƒ€λ‘œμ„œ, 였직 ν•˜λ‚˜μ˜ μƒνƒœ(State)λ§Œμ„ κ°€μ§€κ²Œ 되고 μ–΄λ– ν•œ 사건(Event)에 μ˜ν•΄ ν•œ μƒνƒœμ—μ„œ λ‹€λ₯Έ μƒνƒœλ‘œ λ³€ν™˜ ν•  수 μžˆλŠ” 전이(Transition)라고 ν•˜λŠ”λ°, μƒνƒœμ™€ μ „μ΄μ˜ 집합을 FSM으둜 μ •μ˜ν•˜λŠ” 것 κ°™λ‹€.

많이 μ•Œλ €μ§„ ν™œμš© 뢄야쀑에 κ²Œμž„μ—μ„œ λͺ¬μŠ€ν„°μ˜ 행동 νŒ¨ν„΄, κ³΅ν•™μ—μ„œμ˜ 논리 νšŒλ‘œλ“± λ‹€μ–‘ν•œ λΆ„μ•Όμ—μ„œ 쓰이고 있고, μš”μ•½ν•˜μžλ©΄ μ•„λž˜μ™€ 같은 μ‘°κ±΄λ“€λ‘œ μ •μ˜κ°€ λœλ‹€.

  • μœ ν•œκ°œμ˜ μƒνƒœ (states)
  • μœ ν•œκ°œμ˜ 사건 (events)
  • 초기 μƒνƒœ
  • ν˜„μž¬ μƒνƒœμ™€ 사건을 λ‹€μŒ μƒνƒœλ‘œ κ²°μ •ν•˜λŠ” 전이 ν•¨μˆ˜ (transition function)
  • (λΉ„μ–΄μžˆμ„ 수 μžˆλŠ”) μ΅œμ’… μƒνƒœ 집합 (final states)

μ£Όμš” λ‚΄μš©μ€ 곡식 λ¬Έμ„œλ₯Ό λ°”νƒ•μœΌλ‘œ μ§„ν–‰ν•˜λ©° μ„€μΉ˜λŠ” μ•„λž˜μ™€ 같이 ν•œλ‹€.

npm install xstate --save
yarn add xstate
Enter fullscreen mode Exit fullscreen mode

κ°œμš”

factory function인 createMachine을 μ΄μš©ν•˜μ—¬ state machine ν˜Ήμ€ statecharts 을 생성할 수 μžˆλ‹€.

import { createMachine } from 'xstate';

const promiseMachine = createMachine({
  id: 'promise',
  initial: 'pending',
  states: {
    pending: {
      on: {
        RESOLVE: { target: 'resolved' },
        REJECT: { target: 'rejected' }
      }
    },
    resolved: {
      type: 'final'
    },
    rejected: {
      type: 'final'
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

idλŠ” machine의 κ³ μœ κ°’μ΄λ©΄μ„œ μžμ‹ μƒνƒœ λ…Έλ“œ id의 base string이 λœλ‹€.
initial은 초기 μƒνƒœ λ…Έλ“œλ₯Ό μ§€μ •ν•˜λ©°, statesλŠ” 각각의 μžμ‹ μƒνƒœλ₯Ό μ •μ˜ν•œλ‹€.
μœ„ FSM의 κ°œλ…μ—μ„œμ˜ 전이(transition)은 resolved와 rejectedλΌλŠ” μƒνƒœλ‘œ μ§€μ •ν•˜λ©°, 각 각의 μƒνƒœλŠ” final μ΄λΌλŠ” machine이 μ’…λ£Œμ— λ„λ‹¬ν•˜λŠ” μƒνƒœμž„μ„ μ§€μ •ν•œλ‹€.

μƒμ„±ν•œ machine을 ν•΄μ„ν•˜κ³  μ‹€ν–‰ν•˜λ €λ©΄ interpretλ₯Ό μ΄μš©ν•˜μ—¬ interpreterλ₯Ό μΆ”κ°€ν•΄μ•Ό ν•œλ‹€. μ•„λž˜μ™€ 같이 μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.

import { createMachine, interpret } from 'xstate';

const promiseMachine = createMachine({
  /* ... */
});

const promiseService = interpret(promiseMachine).onTransition((state) =>
  console.log(state.value)
);

// Start the service
promiseService.start();
// => 'pending'

promiseService.send({ type: 'RESOLVE' });
// => 'resolved'
Enter fullscreen mode Exit fullscreen mode

그리고 createMachine으둜 μƒμ„±ν•œ μ½”λ“œλŠ” 이 κ³³μ—μ„œ μ‹œκ°ν™” ν•˜κ³  μ‹€ν–‰ν•΄ λ³Ό 수 μžˆλ‹€.

States

μƒνƒœλŠ” νŠΉμ • μ‹œμ μ˜ 좔상적 ν‘œν˜„μ΄λ‹€. FSM은 주어진 μ‹œκ°„μ— μœ ν•œν•œ 수의 μƒνƒœμ€‘ ν•˜λ‚˜λ§Œ κ°€λŠ₯ν•˜λ‹€. μƒνƒœ μ •μ˜λŠ” μ•„λž˜μ™€ 같이 μ •μ˜ ν•˜κ³  확인할 수 μžˆλ‹€.

const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      /* ... */
    }
    // ...
  }
});

console.log(lightMachine.initialState);
// State {
//   value: 'green',
//   actions: [],
//   context: undefined,
//   // ...
// }
Enter fullscreen mode Exit fullscreen mode

State κ°μ²΄λŠ” JSON 직렬화가 κ°€λŠ₯ν•˜λ©° μ•„λž˜μ™€ 같은 속성이 μžˆλ‹€.

  • value: ν˜„μž¬ μƒνƒœ κ°’ (예λ₯Ό λ“€μ–΄ { red: 'walk' } )
  • context: μƒνƒœμ˜ ν˜„μž¬ context
  • event: ν•΄λ‹Ή μƒνƒœλ‘œ transition을 νŠΈλ¦¬κ±°ν•œ event 였브젝트
  • actions: μ‹€ν–‰ν•  μ•‘μ…˜μ˜ λ°°μ—΄
  • activities: μ•‘ν‹°λΉ„ν‹°κ°€ μ‹€ν–‰λ˜λ©΄ true, μ€‘μ§€λ˜λ©΄ false
  • history: 이전 μƒνƒœ μΈμŠ€ν„΄μŠ€
  • meta: μƒνƒœ λ…Έλ“œμ˜ meta ν”„λ‘œνΌν‹°λ‘œ μ •μ˜λœ 메타 데이터
  • done: μƒνƒœκ°€ μ΅œμ’… μƒνƒœλ₯Ό λ‚˜νƒ€λ‚΄λŠ”μ§€μ— λŒ€ν•œ μ—¬λΆ€

State의 λ©”μ†Œλ“œμ™€ 속성듀

state.matches(parentStateValue)

state.valueκ°€ 주어진 parentStateValue의 subset인지 ν™•μΈν•œλ‹€. 예λ₯Όλ“€μ–΄ state.valueκ°€ { red: β€˜stop’ } 이라면?

console.log(state.value);
// => { red: 'stop' }

console.log(state.matches('red'));
// => true

console.log(state.matches('red.stop'));
// => true

console.log(state.matches({ red: 'stop' }));
// => true

console.log(state.matches('green'));
// => false
Enter fullscreen mode Exit fullscreen mode

λ§Œμ•½ μ—¬λŸ¬ μƒνƒœμ— λŒ€ν•œ matchλ₯Ό μ›ν•œλ‹€λ©΄?

const isMatch = [{ customer: 'deposit' }, { customer: 'withdrawal' }].some(
  state.matches
);

Enter fullscreen mode Exit fullscreen mode

state.nextEvents

ν˜„μž¬ μƒνƒœμ—μ„œ κ°€λŠ₯ν•œ λ‹€μŒ transition을 확인해볼 수 μžˆλ‹€.

const { initialState } = lightMachine;
console.log(initialState.nextEvents);
// => ['TIMER', 'EMERGENCY']
Enter fullscreen mode Exit fullscreen mode

state.changed

이전 μƒνƒœλ‘œλΆ€ν„° λ³€ν™”λ˜μ—ˆλŠ”μ§€μ— λŒ€ν•œ μ—¬λΆ€λ₯Ό μ•Œμˆ˜ μžˆλ‹€. μƒνƒœκ°€ μ•„λž˜μ™€ κ°™λ‹€λ©΄ changed둜 κ°„μ£Όν•œλ‹€.

  • 이전 κ°’κ³Ό 값이 같지 μ•Šλ‹€
  • μ–΄λ– ν•œ μƒˆλ‘œμš΄ μ•‘μ…˜ (side-effects)κ°€ μ‹€ν–‰λ˜μ—ˆλ‹€.
const { initialState } = lightMachine;

console.log(initialState.changed);
// => undefined

const nextState = lightMachine.transition(initialState, { type: 'TIMER' });

console.log(nextState.changed);
// => true

const unchangedState = lightMachine.transition(nextState, {
  type: 'UNKNOWN_EVENT'
});

console.log(unchangedState.changed);
// => false
Enter fullscreen mode Exit fullscreen mode

졜초 μƒνƒœ(νžˆμŠ€ν† λ¦¬κ°€ μ—†λŠ” 것)λŠ” undefined이닀.

state.done

이 μƒνƒœκ°€ μ΅œμ’… μƒνƒœμΈμ§€μ— λŒ€ν•œ μ—¬λΆ€λ₯Ό μ•Œ 수 μžˆλ‹€.

const answeringMachine = createMachine({
  initial: 'unanswered',
  states: {
    unanswered: {
      on: {
        ANSWER: { target: 'answered' }
      }
    },
    answered: {
      type: 'final'
    }
  }
});

const { initialState } = answeringMachine;
initialState.done; // false

const answeredState = answeringMachine.transition(initialState, {
  type: 'ANSWER'
});
answeredState.done; // true
Enter fullscreen mode Exit fullscreen mode

state.toStrings()

μƒνƒœκ°’μ˜ νŒ¨μŠ€μ— λŒ€ν•œ λͺ¨λ“  ν‘œν˜„μ˜ λ¬Έμžμ—΄ 배열을 λ¦¬ν„΄ν•œλ‹€.

console.log(state.value);
// => { red: 'stop' }

console.log(state.toStrings());
// => ['red', 'red.stop']

Enter fullscreen mode Exit fullscreen mode

λ¬Έμ„œμ—μ„œλŠ” CSS classλ‚˜ data-attributes 같은 string-based ν™˜κ²½μ—μ„œ μœ μš©ν•˜κ²Œ μ‚¬μš©λ  수 μžˆλ‹€κ³  ν•˜λŠ”λ° μ•„λ§ˆ ν•΄λ‹Ή μš”μ†Œμ— state의 value값을 λ°”λ‘œ μ‚¬μš©ν•˜λ©΄ λ˜κ² μ§€ λΌλŠ” 생각이 λ“€μ—ˆλ‹€.

이후 λ‚΄μš©μ€ 쑰금 더 λ‚΄μš©μ„ 읽어보고 μž‘μ„±ν•΄μ•Όν•  것 κ°™λ‹€.

to be continue..

Top comments (0)

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs