본문 바로가기

FrontEnd

XState : 컨텍스트와 이벤트 모델링

반응형

https://xstate.js.org/docs/guides/models.html

 

Models | XState Docs

Models In XState, you can model a machine's context and events externally by using createModel(...). This provides a convenient way to strongly type context and events, as well as helpers for event creation, assignment and other implementation details in t

xstate.js.org

 
XState에서 createModel(...)을 사용하여 외부적으로 머신의 컨텍스트(양적 데이터)와 이벤트를 모델링할 수 있습니다.
이는 컨텍스트 및 이벤트를 강력하게 타이핑하는 편리한 방법을 제공할 뿐만 아니라 향후 이벤트 생성, 할당 및 기타 구현 세부 사항을 위한 도우미 함수를 제공합니다.
createModel(...) 사용은 완전히 선택 사항이며 개발자 경험을 개선하기 위한 것입니다.
사용하는 주요 이유는 다음과 같습니다.
 
  • 강타입으로 컨텍스트와 이벤트를 분리하여  머신을 구성합니다.
  • assign(...)으로 타이핑 문제 방지
  • 보다 쉽고 안전한 이벤트 생성을 위한 이벤트 생성자 지정
  • 잠재적으로 다른 시스템과 모델 공유
  • 액션, 가드 지정 등 향후 개발자 경험 개선 

createModel(...)

createModel(...) 함수는 다음과 같은 인자를 허용합니다.


Argument Type Description
initialContext object The initial context value
creators (optional) object An object containing various event creators

 

Creators 개체에는 다음 속성이 포함됩니다.


Argument Type Description
events object An object containing event creators

creators.events 객체의 키는 이벤트 타입이고

value은 임의의 수의 인수를 허용하고 이벤트 페이로드를 반환하는 함수입니다.

Modeling context (컨텍스트 모델링)


모델은 머신의 컨텍스트를 정의하므로 모델은 머신 정의 내에서 model.initialContext로 초기 컨텍스트를 설정하고 model.assign으로 머신의 컨텍스트를 업데이트하는 데 사용할 수 있습니다.
 
model.assign 함수는 모델 컨텍스트의 모양에 따라 입력되어 assign 작업을 편리하고 타입 안전하게 대체합니다.
import { createModel } from 'xstate/lib/model';

const userModel = createModel({
  name: 'Someone',
  age: 0
});

// ...

const machine = userModel.createMachine({
  context: userModel.initialContext,
  // ...
  entry: userModel.assign({ name: '' })
});

Modeling events (이벤트 모델링)


모델에서 머신 이벤트를 모델링하면 두 가지 이점이 있습니다.
  • model.events.eventName(...)을 호출하여 이벤트를 생성할 수 있습니다.
  • 머신 정의에 타입 정보를 제공하여 이벤트별 액션 정의에 대한 타입 안전함을 제공합니다.
import { createModel } from 'xstate/lib/model';

const userModel = createModel(
  // Initial context
  {
    name: 'David',
    age: 30
  },
  {
    // Event creators
    events: {
      updateName: (value) => ({ value }),
      updateAge: (value) => ({ value }),
      anotherEvent: () => ({}) // no payload
    }
  }
);

const machine = userModel.createMachine(
  {
    context: userModel.initialContext,
    initial: 'active',
    states: {
      active: {
        on: {
          updateName: {
            actions: userModel.assign({
              name: (_, event) => event.value
            })
          },
          updateAge: {
            actions: 'assignAge'
          }
        }
      }
    }
  },
  {
    actions: {
      assignAge: userModel.assign({
        age: (_, event) => event.value // inferred
      })
    }
  }
);

// This sends the following event:
// {
//   type: 'updateName',
//   value: 'David'
// }
const nextState = machine.transition(
  undefined,
  userModel.events.updateName('David')
);

TypeScript (타입스크립트)


createModel(...) 함수는 다음 타입을 추론합니다.
  • 컨텍스트는 createModel(initialContext, creators)의 첫 번째 인수에서 추론됩니다.
  • 이벤트는 createModel(initialContext, creators)의 creators.events에서 유추됩니다.
import { createModel } from 'xstate/lib/model';

const userModel = createModel(
  {
    name: 'David', // inferred as `string`
    age: 30, // inferred as `number`
    friends: [] as string[] // explicit type
  },
  {
    events: {
      updateName: (value: string) => ({ value }),
      updateAge: (value: number) => ({ value }),
      anotherEvent: () => ({}) // no payload
    }
  }
);

// Context inferred as:
// {
//   name: string;
//   age: number;
//   friends: string[];
// }

// Events inferred as:
// | { type: 'updateName'; value: string; }
// | { type: 'updateAge'; value: number; }
// | { type: 'anotherEvent'; }

모델에서 머신 생성

컨텍스트 및 이벤트의 타입을 createMachine<TContext, TEvent>(...)의 타입 매개변수로 명시적으로 지정하는 대신 model.createMachine(...) 메소드를 사용해야 합니다.
const machine = userModel.createMachine({
  context: userModel.initialContext,
  initial: 'active',
  states: {
    active: {
      on: {
        updateName: {
          actions: userModel.assign({
            name: (_, event) => event.value // inferred
          })
        }
      }
    }
  }
});

assign 이벤트 타입 좁히기

assign() 액션이 options.actions에서 참조되면, model.assign(assignments, eventType)의

두 번째 인수에서 액션이 허용하는 이벤트 타입을 좁힐 수 있습니다.

const assignAge = userModel.assign(
  {
    // The `event.type` here is restricted to "updateAge"
    age: (_, event) => event.value // inferred as `number`
  },
  'updateAge' // Restricts the `event` allowed by the "assignAge" action
);

const machine = userModel.createMachine({
  context: userModel.initialContext,
  initial: 'active',
  states: {
    active: {
      on: {
        updateAge: {
          actions: assignAge
        }
      }
    }
  }
});

 

경고
 
제한된 이벤트 타입이 있는 assign 작업은
createMachine(configuration, options)의 options 내 actions: {...} 속성 내에 배치할 수 없습니다.
이는 options.actions의 액션이 머신 구성에서 다르게 제안하더라도 잠재적으로 모든 이벤트를 수신하는 것으로 가정해야 하기 때문입니다. 

Extracting types from model (모델에서 타입 추출하기)

Since 4.22.1 버전 이후부터...

ContextFrom<T> 및 EventFrom<T> 타입을 사용하여 모델에서 컨텍스트 및 이벤트 타입을 추출할 수 있습니다.
import { ContextFrom, EventFrom } from 'xstate';
import { createModel } from 'xstate/lib/model';

const someModel = createModel(
  {
    /* ... */
  },
  {
    events: {
      /* ... */
    }
  }
);

type SomeContext = ContextFrom<typeof someModel>;
type SomeEvent = EventFrom<typeof someModel>;
반응형