import { createMachine } from 'xstate';
const lightMachine = createMachine({
schema: {
// The context (extended state) of the machine
context: {} as { elapsed: number },
// The events this machine handles
events: {} as
| { type: 'TIMER' }
| { type: 'POWER_OUTAGE' }
| { type: 'PED_COUNTDOWN'; duration: number }
}
/* Other config... */
});
- 컨텍스트 타입 / 인터페이스 타입(TContext)은 액션, 가드, 서비스 등에 전달됩니다. 또한 깊게 중첩된 상태로 전달됩니다.
- 이벤트 타입(TEvent)은 지정된 이벤트(및 기본 제공 XState 관련 이벤트)에 대해서만 전환 구성에 사용될 수 있도록 합니다.
- 제공된 이벤트 객체 모양은 작업, 가드 및 서비스에도 전달됩니다.
- 기계로 보내는 이벤트는 강력한 형식으로 지정되어 수신할 페이로드 모양에 대해 훨씬 더 확신을 갖게 됩니다.
Typegen 4.29
이 기능은 베타 버전입니다!
개선을 위해 적극적으로 노력하고 있는 사항을 확인하려면 아래의 알려진 제한 사항 섹션을 참조하세요.
- VS Code 확장을 다운로드 및 설치하거나 CLI를 설치하고 --watch 플래그와 함께 xstate typegen 명령을 실행합니다.
- 새 파일을 열고 스키마 속성을 전달하여 새 시스템을 만듭니다.
import { createMachine } from 'xstate';
const machine = createMachine({
schema: {
context: {} as { value: string },
events: {} as { type: 'FOO'; value: string } | { type: 'BAR' }
},
initial: 'a',
states: {
a: {
on: {
FOO: {
actions: 'consoleLogValue',
target: 'b'
}
}
},
b: {
entry: 'consoleLogValueAgain'
}
}
});
const machine = createMachine({
tsTypes: {},
schema: {
context: {} as { value: string },
events: {} as { type: 'FOO'; value: string } | { type: 'BAR' }
},
initial: 'a',
states: {
/* ... */
}
});
const machine = createMachine({
tsTypes: {} as import('./filename.typegen').Typegen0
/* ... */
});
const machine = createMachine(
{
/* ... */
},
{
actions: {
consoleLogValue: (context, event) => {
// Wow! event is typed to { type: 'FOO' }
console.log(event.value);
},
consoleLogValueAgain: (context, event) => {
// Wow! event is typed to { type: 'FOO' }
console.log(event.value);
}
}
}
);
Typing promise services (프라미스 반환 서비스 타이핑)
import { createMachine } from 'xstate';
createMachine(
{
schema: {
services: {} as {
myService: {
// The data that gets returned from the service
data: { id: string };
};
}
},
invoke: {
src: 'myService',
onDone: {
actions: 'consoleLogId'
}
}
},
{
services: {
myService: async () => {
// This return type is now type-safe
return {
id: '1'
};
}
},
actions: {
consoleLogId: (context, event) => {
// This event type is now type-safe
console.log(event.data.id);
}
}
}
);
VS Code 확장을 최대한 활용하는 방법
권장 사항은 대부분 인라인 액션이 아닌 명명된 액션/가드/서비스를 사용하는 것입니다.
이것은 최적입니다:
createMachine(
{
entry: ['sayHello']
},
{
actions: {
sayHello: () => {
console.log('Hello!');
}
}
}
);
createMachine({
entry: [
() => {
console.log('Hello!');
}
]
});
- 이름이 상태 차트에 표시되기 때문에 더 나은 시각화
- 이해하기 쉬운 코드
- useMachine 또는 machine.withConfig에서 재정의
The generated files
{
"scripts": {
"postinstall": "xstate typegen \"./src/**/*.ts?(x)\""
}
}
Don't use enums
이넘은 XState TypeScript와 함께 사용되는 일반적인 패턴입니다.
그들은 종종 주 이름을 선언하는 데 사용되었습니다. 이와 같이:
enum States {
A,
B
}
createMachine({
initial: States.A,
states: {
[States.A]: {},
[States.B]: {}
}
});
알려진 제한 사항
Always transitions/raised events
Config Objects
import { MachineConfig } from 'xstate';
const myMachineConfig: MachineConfig<TContext, any, TEvent> = {
id: 'controller',
initial: 'stopped',
states: {
stopped: {
/* ... */
},
started: {
/* ... */
}
}
// ...
};
Typestates 4.7+
Typestates는 상태 값을 기반으로 전체 상태 컨텍스트의 모양을 좁히는 개념입니다.
이것은 과도한 타입 단언을 작성하지 않고도 불가능한 상태를 방지하고 주어진 상태에서 컨텍스트가 어떻게 되어야 하는지를 좁히는 데 도움이 될 수 있습니다.
- value - typestate의 상태 값(복합 상태는 개체 구문을 사용하여 참조해야 합니다. 예: 'idle.error' 대신 { idle: 'error' })
- context - 상태가 주어진 값과 일치할 때 typestate의 축소된 컨텍스트
Example:
import { createMachine, interpret } from 'xstate';
interface User {
name: string;
}
interface UserContext {
user?: User;
error?: string;
}
type UserEvent =
| { type: 'FETCH'; id: string }
| { type: 'RESOLVE'; user: User }
| { type: 'REJECT'; error: string };
type UserTypestate =
| {
value: 'idle';
context: UserContext & {
user: undefined;
error: undefined;
};
}
| {
value: 'loading';
context: UserContext;
}
| {
value: 'success';
context: UserContext & { user: User; error: undefined };
}
| {
value: 'failure';
context: UserContext & { user: undefined; error: string };
};
const userMachine = createMachine<UserContext, UserEvent, UserTypestate>({
id: 'user',
initial: 'idle',
states: {
idle: {
/* ... */
},
loading: {
/* ... */
},
success: {
/* ... */
},
failure: {
/* ... */
}
}
});
const userService = interpret(userMachine);
userService.subscribe((state) => {
if (state.matches('success')) {
// from the UserState typestate, `user` will be defined
state.context.user.name;
}
});
type State =
/* ... */
| {
value: 'parent';
context: Context;
}
| {
value: { parent: 'child' };
context: Context;
};
/* ... */
type State =
/* ... */
{
value: 'parent' | { parent: 'child' };
context: Context;
};
/* ... */
Troubleshooting
Events in machine options
import { createMachine } from 'xstate';
interface Context {}
type Event =
| { type: 'EVENT_WITH_FLAG'; flag: boolean }
| {
type: 'EVENT_WITHOUT_FLAG';
};
createMachine(
{
schema: {
context: {} as Context,
events: {} as Event
},
on: {
EVENT_WITH_FLAG: {
actions: 'consoleLogData'
}
}
},
{
actions: {
consoleLogData: (context, event) => {
// This will error at .flag
console.log(event.flag);
}
}
}
);
이 오류가 발생하는 이유는 consoleLogData 함수 내부에서 어떤 이벤트가 발생했는지 알 수 없기 때문입니다.
이를 관리하는 가장 깔끔한 방법은 이벤트 타입을 직접 단언하는 것입니다. (타입 가드)
createMachine(config, {
actions: {
consoleLogData: (context, event) => {
if (event.type !== 'EVENT_WITH_FLAG') return
// No more error at .flag!
console.log(event.flag);
};
}
})
import { createMachine } from 'xstate';
createMachine({
schema: {
context: {} as Context,
events: {} as Event
},
on: {
EVENT_WITH_FLAG: {
actions: (context, event) => {
// No more error, because we know which event
// is responsible for calling this action
console.log(event.flag);
}
}
}
});
이 접근 방식은 모든 경우에 작동하지 않습니다. 액션은 이름을 잃어버리기 때문에 비주얼라이저에서 보기 좋지 않습니다.
또한 작업이 여러 위치에 복제된 경우 필요한 모든 위치에 복사하여 붙여넣어야 함을 의미합니다.
Event types in entry actions
import { createMachine } from 'xstate';
interface Context {}
type Event =
| { type: 'EVENT_WITH_FLAG'; flag: boolean }
| {
type: 'EVENT_WITHOUT_FLAG';
};
createMachine({
schema: {
context: {} as Context,
events: {} as Event
},
initial: 'state1',
states: {
state1: {
on: {
EVENT_WITH_FLAG: {
target: 'state2'
}
}
},
state2: {
entry: [
(context, event) => {
// This will error at .flag
console.log(event.flag);
}
]
}
}
});
여기서 state2에 대한 entry action로 이어진 event가 무엇인지 알 수 없습니다.
이 문제를 해결하는 유일한 방법은 위와 유사한 트릭을 수행하는 것입니다.
// 타입 가드
entry: [
(context, event) => {
if (event.type !== 'EVENT_WITH_FLAG') return;
// No more error at .flag!
console.log(event.flag);
}
];
Assign action behaving strangely
interface Context {
something: boolean;
}
createMachine({
schema: {
context: {} as Context
},
context: {
something: true
},
entry: [
// Type 'AssignAction<{ something: false; }, AnyEventObject>' is not assignable to type 'string'.
assign(() => {
return {
something: false
};
}),
// Type 'AssignAction<{ something: false; }, AnyEventObject>' is not assignable to type 'string'.
assign({
something: false
}),
// Type 'AssignAction<{ something: false; }, AnyEventObject>' is not assignable to type 'string'.
assign({
something: () => false
})
]
});
시도한 모든 것이 작동하지 않는 것처럼 보일 수 있습니다. 모든 구문은 버그가 있습니다.
수정 사항은 매우 이상하지만 일관되게 작동합니다. 할당자 함수의 첫 번째 인수에 사용되지 않은 컨텍스트 인수를 추가합니다.
entry: [
// No more error!
assign((context) => {
return {
something: false,
};
}),
// No more error!
assign({
something: (context) => false,
}),
// Unfortunately this technique doesn't work for this syntax
// assign({
// something: false
// }),
],
keyofStringsOnly
Type error: Type 'string | number' does not satisfy the constraint 'string'.
Type 'number' is not assignable to type 'string'. TS2344
'FrontEnd' 카테고리의 다른 글
제어의 역전을 활용해 타입 친화적인 컨텍스트를 디자인하는 방법 (0) | 2022.05.05 |
---|---|
XState와 React를 활용한 기본 UI 예제 구현(7-GUIs) (0) | 2022.05.05 |
XState : 컨텍스트와 이벤트 모델링 (0) | 2022.05.05 |
XState의 Context(양적 데이터) 알아보기 (0) | 2022.05.05 |
XState의 Effect(부수 효과) - Action (0) | 2022.05.04 |