XState (2)

jehoon

Park Je Hoon

Posted on June 23, 2021

XState (2)

1편에 이어서..


States

state.hasTag

state에 tag를 관리하게 할 수 있다. 아래의 코드와 같이 greenyellow가 동일한 상태로서 매칭된다면 state.matches('green') || state.matchs('yellow') 대신 사용할 수 있다.

const machine = createMachine({
  initial: 'green',
  states: {
    green: {
      tags: 'go' // single tag
    },
    yellow: {
      tags: 'go'
    },
    red: {
      tags: ['stop', 'other'] // multiple tags
    }
  }
});

const canGo = state.hasTag('go');
// === state.matches('green') || state.matchs('yellow')
Enter fullscreen mode Exit fullscreen mode

Persisting State

State 객체는 string json format으로 직렬화하여 localstorage 등의 값으로 부터 초기화 될 수 있다.

const jsonState = JSON.stringify(currentState);

// 어딘가에서 저장하고..
try {
  localStorage.setItem('app-state', jsonState);
} catch (e) {
  // unable to save to localStorage
}

// ...

const stateDefinition =
  JSON.parse(localStorage.getItem('app-state')) || myMachine.initialState;

// State.create()를 이용하여 plain object로 부터 스토어를 복구시킴
const previousState = State.create(stateDefinition);

// machine.resolveState()를 이용하여 새 상태로 정의됨
const resolvedState = myMachine.resolveState(previousState);
Enter fullscreen mode Exit fullscreen mode

React와 사용할땐 어떻게 사용할까 하고 보니 @xstate/react에서는 아래와 같이 간단하게 쓰고 있다.

// ...

// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState;

const App = () => {
  const [state, send] = useMachine(someMachine, {
    state: persistedState // provide persisted state config object here
  });

  // state will initially be that persisted state, not the machine's initialState

  return (/* ... */)
}
Enter fullscreen mode Exit fullscreen mode

State Meta Data

state node의 관련 속성을 설명하는 정적 데이터인 메타 데이터는 meta 속성에 지정할 수 있다.

const lightMachine = createMachine({
  id: 'light',
  initial: 'green',
  states: {
    green: {
      tags: 'go',
      meta: {
        message: 'can go',
      },
      on: { 'WILL_STOP': 'yellow' },
    },
    yellow: {
      tags: 'go',
      meta: {
        message: 'will be red',
      },
      on: { 'STOP': 'red' }
    },
    red: {
      tags: ['stop', 'other'],
      meta: {
        message: 'stop!',
      },
      on: { 'GO': 'green' }
    }
  }
});
const yellowState = lightMachine.transition('green', {
  type: 'WILL_STOP'
});

console.log(yellowState.meta);
// { 'light.yellow': { message: 'will be red' } }
Enter fullscreen mode Exit fullscreen mode

(공식문서 보고 {'yellow': { message: 'will be red' }}를 기대했었는데..)

meta를 여러개 포함할때도 모두 표현해준다.

const fetchMachine = createMachine({
  id: 'fetch',
  // ... 중략
    loading: {
      after: {
        3000: 'failure.timeout'
      },
      on: {
        RESOLVE: { target: 'success' },
        REJECT: { target: 'failure' },
        TIMEOUT: { target: 'failure.timeout' } // manual timeout
      },
      meta: {
        message: 'Loading...'
      }
    },
    failure: {
      initial: 'rejection',
      states: {
        rejection: {
          meta: {
            message: 'The request failed.'
          }
        },
        timeout: {
          meta: {
            message: 'The request timed out.'
          }
        }
      },
      meta: {
        alert: 'Uh oh.'
      }
    },
  // ... 하략
});

const failureTimeoutState = fetchMachine.transition('loading', {
  type: 'TIMEOUT'
});
console.log(fetchMachine.meta)
/*
{
  "fetch.failure.timeout': {
    'message': 'The request timed out.',
  },
  'fetch.failure': {
    'alert": "Uh oh.',
  }
}
*/
Enter fullscreen mode Exit fullscreen mode

State Node

state machine은 전체 상태(overall state)를 집합으로 표현되는 상태 노드(State Node)를 포함한다. 아래의 상태 명세는 위 예제에서 가져왔다.

// 위의 fetchMachine의 loading 참고
// 해당 State의 configuration 내부의 config에서 확인 가능.
{
  'after': {
    '3000': 'failure.timeout'
  },
  'on': {
    'RESOLVE': {
      'target': 'success'
    },
    'REJECT': {
      'target': 'failure'
    },
    'TIMEOUT': {
      'target': 'failure.timeout'
    }
  },
  'meta': {
    'message': 'Loading...'
  }
}
Enter fullscreen mode Exit fullscreen mode

전체 State는 machine.transition()의 리턴 값이나 service.onTransition()의 콜백 값에서도 있다.

const nextState = fetchMachine.transition('idle', { type: 'FETCH' });
// State {
//   value: 'loading',
//   actions: [],
//   context: undefined,
//   configuration: [ ... ]
//   ...
// }

Enter fullscreen mode Exit fullscreen mode

XState에서 상태 노드는 state configuration으로 지정된다. 이들은 machines의 states property에 정의되어있다. 하위 상태(sub-state) 노드 역시 마찬가지로 계층 구조로서 states property의 상태 노드에 선언될 수 있다. 'machine.transition(state, event)'에서 결정된 상태는 상태 노드의 조합을 나타낸다. 예를들어 아래의 success와 하위 상태 items{ success: 'items' }로 표현된다.

const fetchMachine = createMachine({
  id: 'fetch',
  // 이것도 States 이고
  states: {
    success: {
      // 자식 상태를 초기화 하고
      initial: { target: 'items' },

      // 자식 상태임.
      states: {
        items: {
          on: {
            'ITEM.CLICK': { target: 'item' }
          }
        },
        item: {
          on: {
            BACK: { target: 'items' }
          }
        }
      }
    },
  });

Enter fullscreen mode Exit fullscreen mode

상태 노드의 유형

상태 노드는 5가지가 있다.

  • atomic - 자식 상태가 없는 노드 (leaf node)
  • compound - 하나 이상의 상태를 포함하며 이런 하위 상태 중 하나가 키인 intial 상태가 있다.
  • parallel - 2개 이상의 하위 상태를 포함하며 동시에 모든 하위 상태가 있다는 것을 나타내기 위함이어서 초기상태가 없다. (약간의 의역인데 이렇게 이해했음..)
  • final - 추상적으로 "말단" 상태임을 나타내는 단말 노드이다.
  • history - 부모 노드의 가장 최근의 shallow or deep history 상태를 나타내는 추상 노드

아래 선언부를 보니까 조금 더 이해가 되었다.

const machine = createMachine({
  id: 'fetch',
  initial: 'idle',
  states: {
    idle: {
      // 단일 노드
      type: 'atomic',
      on: {
        FETCH: { target: 'pending' }
      }
    },
    pending: {
      // resource1, resource2 두개가 parallel 하게 있구나..
      type: 'parallel',
      states: {
        resource1: {
          // 내부에 pending, success 두가지를 갖는 복합 상태
          type: 'compound',
          initial: 'pending',
          states: {
            pending: {
              on: {
                'FULFILL.resource1': { target: 'success' }
              }
            },
            success: {
              type: 'final'
            }
          }
        },
        resource2: {
          type: 'compound',
          initial: 'pending',
          states: {
            pending: {
              on: {
                'FULFILL.resource2': { target: 'success' }
              }
            },
            success: {
              type: 'final'
            }
          }
        }
      },
      // resource1, resource2 둘 다 final 상태가 되면 success로
      onDone: 'success'
    },
    success: {
      type: 'compound',
      initial: 'items',
      states: {
        items: {
          on: {
            'ITEM.CLICK': { target: 'item' }
          }
        },
        item: {
          on: {
            BACK: { target: 'items' }
          }
        },
        hist: {
          type: 'history',
          history: 'shallow'
        }
      }
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

유형을 명시적으로 지정하면 typescript 분석 및 유형검사 관련으로 유용하다고 하는데, parallel, history, final만 해당된다.


이후 3편에서 Transient State Nodes 부터 이어서 진행 예정입니다.

💖 💪 🙅 🚩
jehoon
Park Je Hoon

Posted on June 23, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

XState (2)
xstate XState (2)

June 23, 2021