최근 상태 관리 라이브러리에 대한 리서치를 하다가 관심있게 보고있던 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
개요
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'
},
},
});
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'
그리고 createMachine
으로 생성한 코드는 이 곳에서 시각화 하고 실행해 볼 수 있다.
States
상태는 특정 시점의 추상적 표현이다. FSM은 주어진 시간에 유한한 수의 상태중 하나만 가능하다. 상태 정의는 아래와 같이 정의 하고 확인할 수 있다.
const lightMachine = createMachine({
id: 'light',
initial: 'green',
states: {
green: {
/* ... */
}
// ...
}
});
console.log(lightMachine.initialState);
// State {
// value: 'green',
// actions: [],
// context: undefined,
// // ...
// }
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
만약 여러 상태에 대한 match를 원한다면?
const isMatch = [{ customer: 'deposit' }, { customer: 'withdrawal' }].some(
state.matches
);
state.nextEvents
현재 상태에서 가능한 다음 transition을 확인해볼 수 있다.
const { initialState } = lightMachine;
console.log(initialState.nextEvents);
// => ['TIMER', 'EMERGENCY']
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
최초 상태(히스토리가 없는 것)는 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
state.toStrings()
상태값의 패스에 대한 모든 표현의 문자열 배열을 리턴한다.
console.log(state.value);
// => { red: 'stop' }
console.log(state.toStrings());
// => ['red', 'red.stop']
문서에서는 CSS class나 data-attributes 같은 string-based 환경에서 유용하게 사용될 수 있다고 하는데 아마 해당 요소에 state의 value값을 바로 사용하면 되겠지 라는 생각이 들었다.
이후 내용은 조금 더 내용을 읽어보고 작성해야할 것 같다.
to be continue..
Top comments (0)