https://xstate.js.org/docs/guides/actors.html#actor-api
- ์ ํ ์์ ๋ฉ์์ง๋ฅผ ๋ค๋ฅธ ์กํฐ์๊ฒ send ํ ์ ์์ต๋๋ค.
- ์ ํ ์์ ์๋ก์ด ์กํฐ๋ฅผ spawnํ ์ ์์ต๋๋ค.
- ์กํฐ์ ๋ก์ปฌ ์ํ๋ ๋ณ๊ฒฝ๋ ์ ์์ต๋๋ค(behavior์ ๋ฐ๋ผ ๊ฒฐ์ ๋จ).
์ํ ๋จธ์ ๋ฐ ์ํ ์ฐจํธ๋ ํ์ ๋ฐ ๋ ผ๋ฆฌ์ ์ด๋ฒคํธ ๊ธฐ๋ฐ ๋ชจ๋ธ์ด๊ธฐ ๋๋ฌธ์ ์กํฐ ๋ชจ๋ธ๊ณผ ๋งค์ฐ ์ ์๋ํฉ๋๋ค.
์ํ ๋จธ์ ์ด ์ด๋ฒคํธ๋ก ์ธํด ์ ํ๋ ๋ ๋ค์ ์ํ์๋ ๋ค์์ด ํฌํจ๋ฉ๋๋ค.
- ๋ค์ ๊ฐ ๋ฐ ์ปจํ ์คํธ(์กํฐ์ ๋ก์ปฌ ์ํ)
- ์คํํ ๋ค์ ์์ (์๋ง๋ ์๋ก ์์ฑ๋์์ ์กํฐ ๋๋ ๋ค๋ฅธ ์กํฐ์๊ฒ ๋ณด๋ด๋ ๋ฉ์์ง)
์กํฐ๋ฅผ spawnํ๊ฑฐ๋ invokeํ ์ ์์ต๋๋ค. ์คํฐ๋ ์กํฐ๋ ํธ์ถ๋ ์กํฐ์ ๋ ๊ฐ์ง ์ฃผ์ ์ฐจ์ด์ ์ด ์์ต๋๋ค.
- ๊ทธ๊ฒ๋ค์ ์ธ์ ๋ ์ง ์คํฐ๋ ์ ์์ต๋๋ค. (assign(...)์ก์ ๋ด๋ถ์ spawn(...)์ ํตํด)
- ์ธ์ ๋ ์ง ์ค์งํ ์ ์์ต๋๋ค. (stop(...) ์์ ์ ํตํด).
Actor API
์กํฐ(XState์์ ๊ตฌํ๋จ)์๋ ๋ค์๊ณผ ๊ฐ์ ์ธํฐํ์ด์ค๊ฐ ์์ต๋๋ค.
- id ์์ฑ : ๋ก์ปฌ ์์คํ ์์ ์กํฐ๋ฅผ ๊ณ ์ ํ๊ฒ ์๋ณํ๋
- .send(...) ๋ฉ์๋ : ์ด ์กํฐ์ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๋ ๋ฐ ์ฌ์ฉ
- .getSnapshot() ๋ฉ์๋. : ์กํฐ์ ๋ง์ง๋ง ๋ฐฉ์ถ ๊ฐ์ ๋๊ธฐ์ ์ผ๋ก ๋ฐํ
์ ํ์ ๋ฉ์๋๊ฐ ์์ ์ ์์ต๋๋ค.
- .stop() ๋ฉ์๋ : ์กํฐ๋ฅผ ์ค์งํ๊ณ ํ์ํ ์ ๋ฆฌ๋ฅผ ์ํํ๋
- .subscribe(...) ๋ฉ์๋ :์ต์ ๋ฒ๋ธ ์กํฐ๋ฅผ ์ํ
- Invoked promises ๋ ์์ ๋ ์ด๋ฒคํธ๋ฅผ ๋ฌด์ํ๊ณ ์ต๋ ํ๋์ ์ด๋ฒคํธ๋ฅผ ๋ถ๋ชจ์๊ฒ ๋ค์ ๋ณด๋ด๋ ์กํฐ์ ๋๋ค.
- Invoked callbacks์ ๋ถ๋ชจ์๊ฒ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๊ณ (์ฒซ ๋ฒ์งธ ์ฝ๋ฐฑ ํ๋ผ๋ฏธํฐ), ์ด๋ฒคํธ๋ฅผ ์์ (๋ ๋ฒ์งธ onReceive ํ๋ผ๋ฏธํฐ)ํ๊ณ , ์ด์ ๋ํด ์กฐ์น๋ฅผ ์ทจํ ์ ์๋ ์กํฐ์ ๋๋ค.
- Invoked machines ์ ์ด๋ฒคํธ๋ฅผ ๋ถ๋ชจ(sendParent(...) ์ก์ ) ๋๋ ํด๋น ๋จธ์ ์ด ์ฐธ์กฐํ๋ ๋ค๋ฅธ ์กํฐ์ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๊ณ (send(...) ์ก์ ), ์ด๋ฒคํธ๋ฅผ ์์ ํ๊ณ , ์ด์ ๋ํด ๋ฐ์(์ํ ์ ํ ๋ฐ ์ก์ )ํ ์ ์๋ ์กํฐ์ ๋๋ค. ์๋ก์ด ์กํฐ๋ฅผ ์์ฑํ๊ณ (spawn(...) ํจ์), ์กํฐ๋ฅผ ์ค์งํฉ๋๋ค.
- Invoked observables ์ ๋ฐฉ์ถ๋ ๊ฐ์ด ๋ถ๋ชจ์๊ฒ ๋ค์ ๋ณด๋ผ ์ด๋ฒคํธ๋ฅผ ๋ํ๋ด๋ ์กํฐ์ ๋๋ค.
- ์๋น์ค์ ๊ฒฝ์ฐ ํ์ฌ ์ํ๊ฐ ๋ฐฉ์ถ๋ฉ๋๋ค.
- Promise์ ๊ฒฝ์ฐ resolved๋ ๊ฐ(๋๋ unfulfilled์ ๊ฒฝ์ฐ undefined)์ด ๋ฐฉ์ถ๋ฉ๋๋ค
- ๋๊ธฐ(pending): ์ดํํ์ง๋, ๊ฑฐ๋ถํ์ง๋ ์์ ์ด๊ธฐ ์ํ.
- ์ดํ(fulfilled): ์ฐ์ฐ์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋จ.
- ๊ฑฐ๋ถ(rejected): ์ฐ์ฐ์ด ์คํจํจ.
- Observable์ ๊ฒฝ์ฐ ๊ฐ์ฅ ์ต๊ทผ์ ๋ฐฉ์ถ๋ ๊ฐ์ด ๋ฐฉ์ถ๋ฉ๋๋ค.
- ์ฝ๋ฐฑ์ ๊ฒฝ์ฐ ์๋ฌด ๊ฒ๋ ๋ด๋ณด๋ด์ง ์์ต๋๋ค.
์กํฐ ์คํฌ๋(Spawn)
Akka ๋๋ Erlang๊ณผ ๊ฐ์ ์กํฐ ๋ชจ๋ธ ๊ธฐ๋ฐ ์ธ์ด์ ๋ง์ฐฌ๊ฐ์ง๋ก ์กํฐ๊ฐ ์์ฑ๋๊ณ ์ปจํ ์คํธ์์ ์ฐธ์กฐ๋ฉ๋๋ค. (assign(...) ์์ ์ ๊ฒฐ๊ณผ๋ก).
- 'xstate'์์ ์คํฐ ๊ธฐ๋ฅ ๊ฐ์ ธ์ค๊ธฐ assign(...)
- ์ก์ ์์ spawn(...)์ ์ฌ์ฉํ์ฌ ์ ์กํฐ ์ฐธ์กฐ๋ฅผ ๋ง๋ญ๋๋ค.
- entity - ์กํฐ์ ํ๋์ ๋ํ๋ด๋ (reactive) ๊ฐ ๋๋ ๊ธฐ๊ณ. ์ํฐํฐ์ ๊ฐ๋ฅํ ํ์
:
- ๋จธ์
- ์ฝ๋ฐฑ
- ํ๋ก๋ฏธ์ค
- ์ต์ ๋ฒ๋ธ
- name(์ ํ ์ฌํญ) - ์กํฐ๋ฅผ ๊ณ ์ ํ๊ฒ ์๋ณํ๋ ๋ฌธ์์ด์ ๋๋ค. ์ด๊ฒ์ ์์ฑ๋ ๋ชจ๋ ์กํฐ์ ํธ์ถ๋ ์๋น์ค์ ๋ํด ๊ณ ์ ํด์ผ ํฉ๋๋ค.
spawn์ ๋ค์ ์ต์ ์ ํฌํจํ ์ ์๋ ๋ ๋ฒ์งธ ์ธ์๋ก options ๊ฐ์ฒด๋ฅผ ํ์ฉํฉ๋๋ค.
- name(์ ํ ์ฌํญ) - ์กํฐ๋ฅผ ๊ณ ์ ํ๊ฒ ์๋ณํ๋ ๋ฌธ์์ด์ ๋๋ค. ์ด๊ฒ์ ์์ฑ๋ ๋ชจ๋ ์กํฐ์ ํธ์ถ๋ ์๋น์ค์ ๋ํด ๊ณ ์ ํด์ผ ํฉ๋๋ค. autoForward - (์ ํ ์ฌํญ) ์ด ์์คํ ์ ์ ์ก๋ ๋ชจ๋ ์ด๋ฒคํธ๊ฐ ํธ์ถ๋ ์์์๊ฒ๋ ์ ์ก(๋๋ ์ ๋ฌ)๋์ด์ผ ํ๋ ๊ฒฝ์ฐ true(๊ธฐ๋ณธ์ ์ผ๋ก false)
- sync - (์ ํ ์ฌํญ) ์ด ๋จธ์ ์ด ์์ฑํ ์์ ๋จธ์ ์ ์ํ๋ฅผ ์๋์ผ๋ก ๊ตฌ๋ ํด์ผ ํ๋ ๊ฒฝ์ฐ true, ์ํ๋ ์์ ๋จธ์ ์ฐธ์กฐ์ .state๋ก ์ ์ฅ๋ฉ๋๋ค.
import { createMachine, spawn } from 'xstate';
import { todoMachine } from './todoMachine';
const todosMachine = createMachine({
// ...
on: {
'NEW_TODO.ADD': {
actions: assign({
todos: (context, event) => [
...context.todos,
{
todo: event.todo,
// add a new todoMachine actor with a unique name
ref: spawn(todoMachine, `todo-${event.id}`)
}
]
})
}
// ...
}
});
spawn(...)์ ์ด๋ฆ ์ธ์๋ฅผ ์ ๊ณตํ์ง ์์ผ๋ฉด ๊ณ ์ ํ ์ด๋ฆ์ด ์๋์ผ๋ก ์์ฑ๋ฉ๋๋ค.
์ด ์ด๋ฆ์ ๊ฒฐ์ ์ ์ด์ง ์์ต๋๋ค โ ๏ธ.
ํ
๊ฒฝ๊ณ
ํ ๋น(assign-assignment) ํจ์ ์ธ๋ถ์์ spawn(...)์ ํธ์ถํ์ง ๋ง์ญ์์ค.
์ด๊ฒ์ ํจ๊ณผ๊ฐ ์๋ ๊ณ ์ ์กํฐ(๋ถ๋ชจ ์๋)๋ฅผ ์์ฑํฉ๋๋ค.
// โ Never call spawn(...) externally
const someActorRef = spawn(someMachine);
// โ spawn(...) is not an action creator
{
actions: spawn(someMachine);
}
// โ Do not assign spawn(...) outside of an assignment function
{
actions: assign({
// remember: this is called immediately, before a service starts
someActorRef: spawn(someMachine)
});
}
// โ
Assign spawn(...) inside an assignment function
{
actions: assign({
someActorRef: () => spawn(someMachine)
});
}
Sending Events to Actors (์กํฐ๋ก ์ด๋ฒคํธ ๋ณด๋ด๊ธฐ)
send() ์์ ์ ์ฌ์ฉํ๋ฉด ๋์ ํํ์์ ํตํด ์ด๋ฒคํธ๋ฅผ ์กํฐ์๊ฒ ๋ณด๋ผ ์ ์์ต๋๋ค.
(ํด๋น ์ํ์ ์ง์ ํ, ์กํฐ์ ์ ํธ๋ฅผ ๋ณด๋)
const machine = createMachine({
// ...
states: {
active: {
entry: assign({
someRef: () => spawn(someMachine)
}),
on: {
SOME_EVENT: {
// Use a target expression to send an event
// to the actor reference
actions: send({ type: 'PING' }, { to: (context) => context.someRef })
}
}
}
}
});
const loginMachine = createMachine({
// ...
entry: assign({
formRef: () => spawn(formMachine, 'form')
}),
states: {
idle: {
on: {
LOGIN: {
actions: send({ type: 'SUBMIT' }, { to: 'form' })
}
}
}
}
});
Stopping Actors (์กํฐ ์ข ๋ฃ)
const someMachine = createMachine({
// ...
entry: [
// Stopping an actor by reference
stop((context) => context.someActorRef),
// Stopping an actor by ID
stop('some-actor')
]
});
Spawning Promises (ํ๋ก๋ฏธ์ค ์กํฐ ์์ฑ)
์์ ๋ ์ด๋ฒคํธ๋ฅผ ๋ฌด์ํ๊ณ ์ต๋ ํ๋์ ์ด๋ฒคํธ๋ฅผ ๋ถ๋ชจ์๊ฒ ๋ค์ ๋ณด๋ด๋ ์กํฐ์ ๋๋ค.
// Returns a promise
const fetchData = (query) => {
return fetch(`http://example.com?query=${event.query}`).then((data) =>
data.json()
);
};
// ...
{
actions: assign({
ref: (_, event) => spawn(fetchData(event.query))
});
}
// ...
Spawning Callbacks (์ฝ๋ฐฑ ์กํฐ ์์ฑ)
๋ถ๋ชจ์๊ฒ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๊ณ (์ฒซ ๋ฒ์งธ ์ฝ๋ฐฑ ํ๋ผ๋ฏธํฐ), ์ด๋ฒคํธ๋ฅผ ์์ (๋ ๋ฒ์งธ onReceive ํ๋ผ๋ฏธํฐ)ํ๊ณ ,
์ด์ ๋ํด ์กฐ์น๋ฅผ ์ทจํ ์ ์๋ ์กํฐ์ ๋๋ค.
์๋ฌด ๊ฐ๋ ๋ฐฉ์ถํ์ง ์์ต๋๋ค.
const counterInterval = (callback, receive) => {
let count = 0;
const intervalId = setInterval(() => {
callback({ type: 'COUNT.UPDATE', count });
count++;
}, 1000);
receive(event => {
if (event.type === 'INC') {
count++;
}
});
return () => { clearInterval(intervalId); }
}
const machine = createMachine({
// ...
{
actions: assign({
counterRef: () => spawn(counterInterval)
})
}
// ...
});
const machine = createMachine({
// ...
on: {
'COUNTER.INC': {
actions: send({ type: 'INC' }, { to: (context) => context.counterRef })
}
}
// ...
});
Spawning Observables (์ต์ ๋ฒ๋ธ ์กํฐ ์์ฑ)
Invoked observables ์ ๋ฐฉ์ถ๋ ๊ฐ์ด ๋ถ๋ชจ์๊ฒ ๋ค์ ๋ณด๋ผ ์ด๋ฒคํธ๋ฅผ ๋ํ๋ด๋ ์กํฐ์ ๋๋ค.
Observable์ ๊ฒฝ์ฐ ๊ฐ์ฅ ์ต๊ทผ์ ๋ฐฉ์ถ๋ ๊ฐ์ด ๋ฐฉ์ถ๋ฉ๋๋ค.
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';
const createCounterObservable = (ms) => interval(ms)
.pipe(map(count => ({ type: 'COUNT.UPDATE', count })))
const machine = createMachine({
context: { ms: 1000 },
// ...
{
actions: assign({
counterRef: ({ ms }) => spawn(createCounterObservable(ms))
})
}
// ...
on: {
'COUNT.UPDATE': { /* ... */ }
}
});
Spawning Machines (์ํ ๊ธฐ๊ณ ์์ฑ)
์ํ ๊ธฐ๊ณ๋ ๊ฐ์ฅ ๋ง์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ธฐ ๋๋ฌธ์ ์กํฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ ๋๋ค.
์์ฑํ ๋จธ์ ์ ๋จธ์ ์ด spawn(machine)์ผ๋ก ์ ๋ฌ๋๋ invoking machine๊ณผ ๊ฐ์ต๋๋ค.
์์ : ๋ถ๋ชจ ๋จธ์ ์ธ ๋ก์ปฌ ๋จธ์ ์ LOCAL.WAKE ๋ช ๋ น => ๋ถ๋ชจ๋ ์์(Remote)๋ฅผ ๊นจ์. => ์์์ online์ํ๊ฐ ๋ ํ 1์ด ํ ๋ถ๋ชจ์ REMOTE.ONLINE ์ด๋ฒคํธ ์ ๋ฌ. ๋ถ๋ชจ๋ connected ์ํ๋ก ์ ํ.
// Remote
const remoteMachine = createMachine({
id: 'remote',
initial: 'offline',
states: {
offline: {
on: {
WAKE: 'online'
}
},
online: {
after: {
1000: {
actions: sendParent('REMOTE.ONLINE')
}
}
}
}
});
// Local
const parentMachine = createMachine({
id: 'parent',
initial: 'waiting',
context: {
localOne: null
},
states: {
waiting: {
entry: assign({
localOne: () => spawn(remoteMachine)
}),
on: {
'LOCAL.WAKE': {
actions: send({ type: 'WAKE' }, { to: (context) => context.localOne })
},
'REMOTE.ONLINE': { target: 'connected' }
}
},
connected: {}
}
});
const parentService = interpret(parentMachine)
.onTransition((state) => console.log(state.value))
.start();
parentService.send({ type: 'LOCAL.WAKE' });
// => 'waiting'
// ... after 1000ms
// => 'connected'
Syncing and Reading State (์ํ ๋๊ธฐํ ๋ฐ ์ํ ์ฝ๊ธฐ)
// ...
{
actions: assign({
// Actor will send update event to parent whenever its state changes
someRef: () => spawn(todoMachine, { sync: true })
});
}
// ...โ
์ด๋ ๊ฒ ํ๋ฉด ๋จธ์ ์ด ์์ฑ๋ ์์ ๋จธ์ ์ ์ํ์ ์๋์ผ๋ก ๋ฑ๋ก๋๋ฉฐ, ์ด ์ํ๋ ๊ณ์ ์ ๋ฐ์ดํธ๋๊ณ getSnapshot()์ ํตํด ์ก์ธ์คํ ์ ์์ต๋๋ค.
someService.onTransition((state) => {
const { someRef } = state.context;
console.log(someRef.getSnapshot());
// => State {
// value: ...,
// context: ...
// }
});โ
someService.onTransition((state) => {
const { someRef } = state.context;
// ์์ ์ํ ์ ๊ทผ
console.log(someRef.state);
// => State {
// value: ...,
// context: ...
// }
});
๊ธฐ๋ณธ์ ์ผ๋ก ๋๊ธฐํ๋ false๋ก ์ค์ ๋ฉ๋๋ค.
๋๊ธฐํ๊ฐ ๋นํ์ฑํ๋ ๊ฒฝ์ฐ ์กํฐ์ .state๋ฅผ ์ฝ์ง ๋ง์ญ์์ค. ๊ทธ๋ ์ง ์์ผ๋ฉด staleํ ์ํ๋ฅผ ์ฐธ์กฐํ๊ฒ ๋ฉ๋๋ค.
์ฃผ : ํด๋น ํจํด์ ๋ถ๋ชจ๋ ์์์ ๋ณผ ์ ์์ผ๋ฉฐ, subscribe ํ์ง๋ง ์์์ ๋ถ๋ชจ๋ฅผ ๋ชจ๋ฆ
Sending Updates (์ ๋ฐ์ดํธ ๋ณด๋ด๊ธฐ)
import { createMachine, sendUpdate } from 'xstate';
const childMachine = createMachine({
// ...
on: {
SOME_EVENT: {
actions: [
// ...
// Creates an action that sends an update event to parent
sendUpdate()
]
}
}
});
TIP
Quick Reference
Import spawn to spawn actors:
import { spawn } from 'xstate';
Spawn actors in assign action creators:
// ...
{
actions: assign({
someRef: (context, event) => spawn(someMachine)
});
}
// ...
Spawn different types of actors: (๋ค์ํ ํ์ ์ ์กํฐ ์์ฑ)
// ...
{
actions: assign({
// From a promise
// ์์ ๋ ์ด๋ฒคํธ๋ฅผ ๋ฌด์ํ๊ณ ์ต๋ ํ๋์ ์ด๋ฒคํธ๋ฅผ ๋ถ๋ชจ์๊ฒ ๋ค์ ๋ณด๋ด๋ ์กํฐ์
๋๋ค.
promiseRef: (context, event) =>
spawn(
new Promise((resolve, reject) => {
// ...
}),
'my-promise'
),
// From a callback
// ๋ฆฌํด ๊ฐ์ ๋ฌด์๋ฏธํจ
callbackRef: (context, event) =>
spawn((callback, receive) => {
// send to parent
callback('SOME_EVENT');
// receive from parent
receive((event) => {
// handle event
});
// disposal
return () => {
/* do cleanup here */
};
}),
// From an observable
// ๋ฆฌํด๊ฐ์ ์ด๋ฒคํธ ๊ฐ์ฒด
observableRef: (context, event) => spawn(someEvent$),
// From a machine
machineRef: (context, event) =>
spawn(
createMachine({
// ...
})
)
});
}
// ...
// ...
{
actions: assign({
someRef: () => spawn(someMachine, { sync: true })
});
}
// ...
service.onTransition((state) => {
const { someRef } = state.context;
someRef.getSnapshot();
// => State { ... }
});
// ...
{
actions: send(
{ type: 'SOME_EVENT' },
{
to: (context) => context.someRef
}
);
}
// ...
// ...
{
actions: send((context, event) => ({ ...event, type: 'SOME_EVENT' }), {
to: (context) => context.someRef
});
}
// ...
// ...
{
actions: sendParent({ type: 'ANOTHER_EVENT' });
}
// ...
// ...
{
actions: sendParent((context, event) => ({
...context,
type: 'ANOTHER_EVENT'
}));
}
// ...
someService.onTransition((state) => {
const { someRef } = state.context;
console.log(someRef);
// => { id: ..., send: ... }
});
'FrontEnd' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
XState์ Context(์์ ๋ฐ์ดํฐ) ์์๋ณด๊ธฐ (0) | 2022.05.05 |
---|---|
XState์ Effect(๋ถ์ ํจ๊ณผ) - Action (0) | 2022.05.04 |
XState ๊ณต์ ๋ฌธ์ ๋ฒ์ญ : ์๋น์ค ํธ์ถ(Invoking Services) (0) | 2022.05.01 |
XState์ ๋น๋๊ธฐ 2ํธ: XState Actor์ ํจ๊ป ๋น๋๊ธฐ๋ฅผ ์์ ํ๊ฒ ๋ชจ๋ธ๋ง (0) | 2022.05.01 |
XState์ ๋น๋๊ธฐ 1ํธ: useEffect ์์ ๋น๋๊ธฐ ์ฝ๋๋ ์ํํฉ๋๋ค! (0) | 2022.04.30 |