본문 바로가기

FrontEnd

XState의 Effect(부수 효과) - Action

반응형

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

 

Effects | XState Docs

Effects In statecharts, "side-effects" can be grouped into two categories: "Fire-and-forget" effects, which execute a synchronous side-effect with no events sent back to the statechart, or send an event synchronously back to the statechart: Actions - singl

xstate.js.org

 

상태 차트에서 "부작용"은 두 가지 범주로 그룹화할 수 있습니다.
"Fire-and-forget" 효과: : 이벤트를 상태 차트로 다시 보내지 않고 동기적 사이드이펙트를 실행하거나 이벤트를 상태 차트로 동기적으로 다시 보내는 효과
  • 액션 : 단일, 이산 효과
  • 액티비티 : 상태가 종료될 때 처리되는 연속 효과
    • Actor로 대체

Invoked Effect(호출된 효과) :이벤트를 비동기적으로 보내고 받을 수 있는 사이드 이펙트를 실행합니다.

  • Invoked Promises - 한 번 resolved되거나 rejected될 수 있는 시간 경과에 따른 단일 개별 효과, 이벤트로 상위 시스템에 전송됩니다.
  • Invoked Callbacks - 시간이 지남에 따라 여러 이벤트를 보낼 수 있는 지속적인 효과,  상위 시스템으로/에서 직접 전송된 이벤트를 수신/ 대기할 수 있습니다.
  • Invoked Observables - Ovserved된 스트림의 메시지에 의해 트리거되는 여러 개의 이벤트를 보낼 수 있는, 시간 경과에 따른 지속적인 효과
    • RxJs와 활용
    • 해당 스펙에 대한 공식 제안이 있음
  • Invoked Machines - 이벤트를 보내고 받을 수 있으며, 최종 상태에 도달했을 때 상위 시스템에 알릴 수 있는 Machine 인스턴스로 표현되는 연속 효과

Actions (액션)

액션은 한번 발생 후 잊혀지는 효과입니다. 세 가지 방법으로 선언할 수 있습니다.
(액션은 발생 그 자체만으로는 나머지 시퀀스에 영향을 주지 않으나, 액션이 상태 차트에 영향을 미치도록 하는것은 가능)
  • entry 동작은 상태에 진입할 때 실행됩니다.
  • exit 동작은 상태를 종료할 때 실행됩니다.
  • transition이 수행될 때 전환 액션이 실행됩니다.
const triggerMachine = createMachine(
  {
    id: 'trigger',
    initial: 'inactive',
    states: {
      inactive: {
        on: {
          TRIGGER: {
            target: 'active',
            // transition actions
            actions: ['activate', 'sendTelemetry']
          }
        }
      },
      active: {
        // entry actions
        entry: ['notifyActive', 'sendTelemetry'],
        // exit actions
        exit: ['notifyInactive', 'sendTelemetry'],
        on: {
          STOP: { target: 'inactive' }
        }
      }
    }
  },
  {
    actions: {
      // action implementations
      activate: (context, event) => {
        console.log('activating...');
      },
      notifyActive: (context, event) => {
        console.log('active!');
      },
      notifyInactive: (context, event) => {
        console.log('inactive!');
      },
      sendTelemetry: (context, event) => {
        console.log('time:', Date.now());
      }
    }
  }
);​

transition vs entry/exit

entry/exit 액션은 "이 상태에 들어가거나 나가는 모든 전환에서 이 액션을 실행"을 의미합니다.

액션이 이전/다음 상태 노드 또는 이벤트가 아닌 해당 상태 노드에만 종속되는 경우 진입/종료 액션을 사용합니다.

// ...
{
  idle: {
    on: {
      LOAD: 'loading'
    }
  },
  loading: {
    // this action is executed whenever the 'loading' state is entered
    entry: 'fetchData'
  }
}
// ...
transition은 "이 transition에서만 이 action을 실행"을 의미합니다.
action이 현재 이벤트 및 상태 노드에 종속되는 경우 transition action을 사용합니다.
(시작점과 종료점이 정해져 있는 경우)
// ...
{
  idle: {
    on: {
      LOAD: {
        target: 'loading',
        // this action is executed only on this transition
        actions: 'fetchData'
    }
  },
  loading: {
    // ...
  }
}
// ...

TIP

action 구현은 머신 구성에서 직접 액션 함수를 지정하여 신속하게 프로토타이핑 할 수 있습니다.
// ...
TRIGGER: {
  target: 'active',
  actions: (context, event) => { console.log('activating...'); }
}
// ...

 

머신 옵션의 actions 속성에서 인라인 액션 구현을 리팩토링하면 더 쉽게 디버그, 직렬화, 테스트 및 액션을 정확하게 시각화할 수 있습니다. 

 

Declarative actions (선언적 액션)


machine.transition(...)에서 반환된 State 인스턴스에는
인터프리터가 실행할 액션 객체의 배열인 .actions 속성이 있습니다.
const activeState = triggerMachine.transition('inactive', { type: 'TRIGGER' });

console.log(activeState.actions);
// [
//   { type: 'activate', exec: ... },
//   { type: 'sendTelemetry', exec: ... },
//   { type: 'notifyActive', exec: ... },
//   { type: 'sendTelemetry', exec: ... }
// ]
각 액션 개체에는 다음과 같은 두 가지 속성(및 지정할 수 있는 다른 속성)이 있습니다.
  • type - the action 타입
  • exec - the action i구현 함수
exec 함수는 세 가지 argument를 취합니다.
Argument Type Description
context TContext
현재 머신 컨텍스트
event event object
전환을 일으킨 이벤트
actionMeta meta object
액션에 대한 메타 데이터를 포함하는 개체(아래 참조)
actionMeta 객체에는 다음 속성이 포함됩니다.
Property Type Description
action action object 원래 액션 객체
state State
전환 후 resolved된 머신 상태

인터프리터는 currentState.context, event및 머신이 전환된 state를 사용하여 exec 함수를 호출합니다.

이 동작을 사용자 지정할 수 있습니다. 자세한 내용은executing actions 을 참조하세요.

 

Action order (액션 실행 순서)


상태 차트를 해석할 때 액션 순서가 반드시 중요하지 않아야 합니다(즉, 서로 의존하지 않아야 함).
그러나 state.actions 배열의 액션 순서는 다음과 같습니다.
  • exit 액션 - 자식 상태로 전환한 상태 노드의 모든 종료 액션
  • transition 액션 - 선택한 전환(부모,자식 순서쌍)에 정의된 모든 액션
  • entry 액션 - 부모 상태에서 진입한 상태 노드의 모든 entry 액션

경고

XState 버전 4.x에서  assign action은 우선 순위를 가지며 다른 action보다 먼저 실행됩니다.

이 동작은 버전 5에서 순서대로 호출되도록 수정됩니다.

 

경고

여기에 문서화된 모든 액션 생성자는 액션 개체를 반환합니다.

액션 객체만 반환하고 이벤트를 명령적으로 보내지 않는 순수 함수입니다.

액션 생성자를 명령적으로 호출하지 마십시오. 그들은 아무것도 하지 않을 것입니다!

// 🚫 Do not do this!
entry: () => {
  // 🚫 This will do nothing; send() is not an imperative function!
  send({ type: 'SOME_EVENT' });
};

console.log(send({ type: 'SOME_EVENT' }));
// => { type: 'xstate.send', event: { type: 'SOME_EVENT' } }

// ✅ Do this instead
entry: send({ type: 'SOME_EVENT' });

Send action (액션 전송)


send(event) 액션 생성자는 서비스(즉, interpreted machine)에 해당 이벤트를 자기 자신에게 보내도록 지시하는 특별한 "send" 액션 객체를 만듭니다.
외부에 존재하는 이벤트 대기열에 실행중인 서비스에 대한 이벤트를 넣습니다. 즉 해당 이벤트는 이벤트는 인터프리터의 다음 "단계"로 전달됩니다.

send(event)

Argument Type Description
event string or event object or event expression The event to send to the specified options.to (or self)
options? send options (see below) Options for sending the event.
send options argument는 다음을 포함하는 객체입니다.
Property Type Description
id? string The send ID (used for cancellation)
to? string The target of the event (defaults to self)
delay? number The timeout (milliseconds) before sending the event, if the event is not canceled before the timeout
경고
send(...) 함수는 액션 생성자입니다.
액션 객체만 반환하고, 이벤트를 명령적으로 보내지 않는 순수 함수입니다.
import { createMachine, send } from 'xstate';

const lazyStubbornMachine = createMachine({
  id: 'stubborn',
  initial: 'inactive',
  states: {
    inactive: {
      on: {
        TOGGLE: {
          target: 'active',
          // send the TOGGLE event again to the service
          actions: send('TOGGLE')
        }
      }
    },
    active: {
      on: {
        TOGGLE: { target: 'inactive' }
      }
    }
  }
});

const nextState = lazyStubbornMachine.transition('inactive', {
  type: 'TOGGLE'
});

nextState.value;
// => 'active'
nextState.actions;
// => [{ type: 'xstate.send', event: { type: 'TOGGLE' }}]

// The service will proceed to send itself the { type: 'TOGGLE' } event.
 
send(event)에 전달할 수 있는 이벤트 인수는 다음과 같습니다.
  • 문자열 이벤트, 예: send('TOGGLE')
  • 이벤트 객체, 예: send({ type: 'TOGGLE', payload: ... })
  • 현재 컨텍스트와 send() 액션을 트리거한 이벤트를 가져와 이벤트 객체를 반환하는 함수인 이벤트 표현식:
import { send } from 'xstate';

// contrived example - reads from the `context` and sends
// the dynamically created event
const sendName = send((context, event) => ({
  type: 'NAME',
  name: context.user.name
}));

const machine = createMachine({
  // ...
  on: {
    TOGGLE: {
      actions: sendName
    }
  }
  //...
});​

Send targets


send(...) 액션 생성자가 보낸 이벤트는 호출된 서비스(invoked services) 또는 생성된 액터(spawned actors)와 같은 특정 대상으로 보내야 함을 나타낼 수 있습니다.

이것은 send(...) 액션에서 { to: ... } 속성을 지정하여 수행됩니다.

// ...
invoke: {
  id: 'some-service-id',
  src: 'someService',
  // ...
},
// ...
// Indicates to send { type: 'SOME_EVENT' } to the invoked service
actions: send({ type: 'SOME_EVENT' }, { to: 'some-service-id' })
to prop의 대상은 타겟 표현식(target expression)이 될 수도 있습니다.
타겟 표현식은 현재 컨텍스트와 액션을 트리거한 이벤트를 받아 문자열 타겟(상태명)이나 액터 참조를 반환하는 함수입니다.
entry: assign({
  someActor: () => {
    return spawn(someMachine, 'some-actor-name');
  }
}),
  // ...

  // Send { type: 'SOME_EVENT' } to the actor ref
  {
    actions: send(
      { type: 'SOME_EVENT' },
      {
        to: (context) => context.someActor
      }
    )
  };​
경고

다시 말하지만, send(...) 함수는 액션 생성자이며 이벤트를 명령적으로 보내지 않습니다.

대신 이벤트가 전송될 위치를 설명하는 액션 개체를 반환합니다.

console.log(send({ type: 'SOME_EVENT' }, { to: 'child' }));
// logs:
// {
//   type: 'xstate.send',
//   to: 'child',
//   event: {
//     type: 'SOME_EVENT'
//   }
// }

자식 시스템에서 부모 시스템으로 보내려면 sendParent(event)를 사용하십시오.

(send(...)와 동일한 인수를 사용).

Raise action


raise() 액션 생성자는 상태 차트의 내부 이벤트 대기열에 이벤트를 넣습니다.

이는 이벤트가 인터프리터의 현재 "단계"에서 즉시 전송됨을 의미합니다.

(주 : 단일 단계에서 전환과 이벤트 해석을 배치처리하게 함.)

Argument Type Description
event string or event object The event to raise.

Raise는 한번에 last로 이동 가능
이동 결과
STEP은 단일 단계 이동

import { createMachine, actions } from 'xstate';
const { raise } = actions;

const raiseActionDemo = createMachine({
  id: 'raisedmo',
  initial: 'entry',
  states: {
    entry: {
      on: {
        STEP: {
          target: 'middle'
        },
        RAISE: {
          target: 'middle',
          // immediately invoke the NEXT event for 'middle'
          actions: raise('NEXT')
        }
      }
    },
    middle: {
      on: {
        NEXT: { target: 'last' }
      }
    },
    last: {
      on: {
        RESET: { target: 'entry' }
      }
    }
  }
});

Click on both STEP and RAISE events in the visualizer (opens new window)to see the difference.

Respond action (호출한 부모 서비스:머신 에 대한 응답 액션)


response() 액션 생성자는 응답을 트리거한 이벤트를 보낸 서비스로 보내는 send() 액션을 만듭니다.
이것은 내부적으로 SCXML 이벤트(SCXML events )를 사용하여 이벤트에서 origin을 가져오고 send() 액션의 to를 origin으로 설정합니다.
(XState의 액션 객체와 사양이 좀 다름)
  • origin - 이 이벤트의 수신자가 응답 이벤트를 원점으로 다시 send(...) 수 있도록 하는 문자열입니다.
Argument Type Description
event string, event object, or send expression The event to send back to the sender
options? send options object Options to pass into the send() event

응답 액션을 사용하는 예

이것은 호출된 authServerMachine에 'CODE' 이벤트를 보내는 어떤 부모 서비스(authClientMachine)와

'TOKEN' 이벤트로 응답하는 authServerMachine을 보여줍니다.

const authServerMachine = createMachine({
  initial: 'waitingForCode',
  states: {
    waitingForCode: {
      on: {
        CODE: {
          // 요청을 보낸 머신에 응답하기.
          actions: respond({ type: 'TOKEN' }, { delay: 10 })
        }
      }
    }
  }
});

const authClientMachine = createMachine({
  initial: 'idle',
  states: {
    idle: {
      on: {
        AUTH: { target: 'authorizing' }
      }
    },
    authorizing: {
      invoke: {
        id: 'auth-server',
        src: authServerMachine
      },
      entry: send('CODE', { to: 'auth-server' }),
      on: {
        TOKEN: { target: 'authorized' }
      }
    },
    authorized: {
      type: 'final'
    }
  }
});

forwardTo action 4.7+


forwardTo() 액션 생성자는 가장 최근에 발생한 이벤트를 해당 ID를 통해 지정된 서비스로 전달하는 send() 액션을 만듭니다.
Argument Type Description
target string or function that returns service The target service to send the most recent event to.

사용 예시

import { createMachine, forwardTo, interpret } from 'xstate';

function alertService(_, receive) {
  receive((event) => {
    if (event.type === 'ALERT') {
      alert(event.message);
    }
  });
}

const parentMachine = createMachine({
  id: 'parent',
  invoke: {
    id: 'alerter',
    src: () => alertService
  },
  on: {
    ALERT: { actions: forwardTo('alerter') }
  }
});

const parentService = interpret(parentMachine).start();

parentService.send({ type: 'ALERT', message: 'hello world' });
// => alerts "hello world"

Escalate action 4.7+


 

escalate() 액션 생성자는 오류를 상위 시스템으로 보내 오류를 에스컬레이션합니다.

이것은 상태 기계가 인식하는 특별한 오류 이벤트로 전송됩니다.

Argument Type Description
errorData any The error data to escalate (send) to the parent
에스컬레이션 액션을 사용한 예
import { createMachine, actions } from 'xstate';
const { escalate } = actions;

const childMachine = createMachine({
  // ...
  // This will be sent to the parent machine that invokes this child
  entry: escalate({ message: 'This is some error' })
});

const parentMachine = createMachine({
  // ...
  invoke: {
    src: childMachine,
    onError: {
      actions: (context, event) => {
        console.log(event.data);
        //  {
        //    type: ...,
        //    data: {
        //      message: 'This is some error'
        //    }
        //  }
      }
    }
  }
});

Log action


log() 액션 작성자는 현재 상태 컨텍스트 및/또는 이벤트와 관련된 모든 것을 기록하는 선언적 방법입니다.

두 개의 선택적 인수가 필요합니다.

Argument Type Description
expr? string or function A plain string or a function that takes the context and event 
as arguments and returns a value to be logged
label? string A string to label the logged message
import { createMachine, actions } from 'xstate';
const { log } = actions;

const loggingMachine = createMachine({
  id: 'logging',
  context: { count: 42 },
  initial: 'start',
  states: {
    start: {
      entry: log('started!'),
      on: {
        FINISH: {
          target: 'end',
          actions: log(
            (context, event) => `count: ${context.count}, event: ${event.type}`,
            'Finish label'
          )
        }
      }
    },
    end: {}
  }
});

const endState = loggingMachine.transition('start', 'FINISH');

endState.actions;
// [
//   {
//     type: 'xstate.log',
//     label: 'Finish label',
//     expr: (context, event) => ...
//   }
// ]

// The interpreter would log the action's evaluated expression
// based on the current state context and event.
인수가 없으면, log()는 각각 현재 컨텍스트와 트리거 이벤트를 포함하는 컨텍스트 및 이벤트 속성을 사용하여 객체를 로깅하는 액션입니다. 

Choose action


choose() 액션 생성자는 일부 조건에 따라 실행해야 하는 작업을 지정하는 작업을 만듭니다.
Argument Type Description
conds array
주어진 조건이 true일 때 실행할 액션을 포함하는 객체 배열(아래 참조)
리턴값 :
실행해야 하는 액션을 결정하기 위해 내부적으로 평가되는 특수 "xstate.choose" 액션 개체입니다.
cond의 각 "조건부 액션" 개체에는 다음 속성이 있습니다.
  • actions - 실행할 액션 객체 조건?
  • cond?- 해당 작업을 실행하기 위한 조건

경고

choose()를 사용하여

entry, exit 또는 actions를 통해 특정 상태/전환에서 실행되는 비조건부 액션으로 표시될 수 있는 액션을 실행하지 마십시오.

import { actions } from 'xstate';

const { choose, log } = actions;

const maybeDoThese = choose([
  {
    cond: 'cond1',
    actions: [
      // selected when "cond1" is true
      log('cond1 chosen!')
    ]
  },
  {
    cond: 'cond2',
    actions: [
      // selected when "cond1" is false and "cond2" is true
      log((context, event) => {
        /* ... */
      }),
      log('another action')
    ]
  },
  {
    cond: (context, event) => {
      // some condition
      return false;
    },
    actions: [
      // selected when "cond1" and "cond2" are false and the inline `cond` is true
      (context, event) => {
        // some other action
      }
    ]
  },
  {
    actions: [
      log('fall-through action')
      // selected when "cond1", "cond2", and "cond3" are false
    ]
  }
]);​
이것은 SCXML <if>, <elseif> 및 <else> 요소와 유사합니다.
www.w3.org/TR/scxml/#if

Pure action (순수 액션)


pure() 액션 생성자는 액션을 트리거한 이벤트와 현재 상태 컨텍스트를 기반으로 실행할 액션 객체를 반환하는 순수 함수입니다.
 
이를 통해 실행해야 하는 액션을 동적으로 정의할 수 있습니다.
 
Argument Type Description
getActions function A function that returns the action object(s) to be executed based on the given context and event (see below)
리턴값 : 
 
실행해야 하는 작업 개체를 결정하기 위해 내부적으로 get 속성을 평가하는 특별한 "xstate.pure" 액션 개체입니다.
getActions(컨텍스트, 이벤트)에 대한 인수:
Argument Type Description
context object The current state context
event event object The event object that triggered the actions
리턴값 : 
단일 액션 개체, 액션 개체 배열 또는 액션 개체가 없음을 나타내는 undefined

Actions on self-transitions (자기 자신으로 상태 전환할 때 액션)


자기 전이(self-trantion)는 상태가 자기 자신으로 전환되는 경우이며, 이 상태에서 exit했다가 다시 entry할 수 있습니다.

자체 전환은 internal 또는 external 전환이 될 수 있습니다.

  • internal 전환은 종료 및 재진입되지 않으므로 상태 노드의 진입 및 종료 작업은 다시 실행되지 않습니다.
    • 내부 전환은 { internal: true }로 표시되거나 대상을 정의되지 않은 상태로 둡니다.
    • 전환의 actions 속성에 정의된 작업이 실행됩니다.
  • external 전환은 종료했다가 다시 들어가므로 상태 노드의 진입 및 종료 작업이 다시 실행됩니다.
    • 모든 전환은 기본적으로 외부입니다. 명시적으로 { internal: false }로 표시할 수 있습니다.
    • 전환의 actions 속성에 정의된 액션이 실행됩니다.

 

이 카운터 머신에는 내부 및 외부 전환이 있는 하나의 'counting' 상태가 있습니다.
const counterMachine = createMachine({
  id: 'counter',
  initial: 'counting',
  states: {
    counting: {
      entry: 'enterCounting',
      exit: 'exitCounting',
      on: {
        // self-transitions
        INC: { actions: 'increment' }, // internal (implicit)
        DEC: { target: 'counting', actions: 'decrement' }, // external
        DO_NOTHING: { internal: true, actions: 'logNothing' } // internal (explicit)
      }
    }
  }
});

// External transition (exit + transition actions + entry)
const stateA = counterMachine.transition('counting', { type: 'DEC' });
stateA.actions;
// ['exitCounting', 'decrement', 'enterCounting']

// Internal transition (transition actions)
const stateB = counterMachine.transition('counting', { type: 'DO_NOTHING' });
stateB.actions;
// ['logNothing']

const stateC = counterMachine.transition('counting', { type: 'INC' });
stateB.actions;
// ['increment']

 

반응형