Invoking Services
Invoking Services
在一台状态机中表达整个应用的行为会很快变得复杂而笨重。很自然地使用多台状态机相互通信来代替表达复杂的逻辑。这与
对于状态机之间的通信,父状态机调用子状态机,并通过
- Promises:将在
resolve 时采用onDone 转换,或在reject 时采用onError 转换。 - Callbacks:可以向父机发送事件和从父机接收事件。
- Observables:可以向父机发送事件,以及完成后的信号。
- Machines:它还可以发送
/ 接收事件,并在达到最终状态时通知父机。
invoke 属性
在状态节点的配置中,用
- src:需要被调用的服务源,可以是状态机、Promise、回调函数、
Observable 等 - id:被调用服务的唯一标识
- onDone:在子状态机达到终态,或者
Promise/Observable 结束时 - onError:当被调用的服务触发异常时
- autoForward:自动转发传入到该状态机的事件到其他状态机
Invoking Promises
由于每个
- resolve():触发
onDone 回调 - reject():触发
onError 回调
如果被调用的
// Function that returns a promise
// This promise might resolve with, e.g.,
// { name: 'David', location: 'Florida' }
const fetchUser = (userId) =>
fetch(`url/to/user/${userId}`).then((response) => response.json());
const userMachine = Machine({
id: "user",
initial: "idle",
context: {
userId: 42,
user: undefined,
error: undefined,
},
states: {
idle: {
on: {
FETCH: "loading",
},
},
loading: {
invoke: {
id: "getUser",
src: (context, event) => fetchUser(context.userId),
onDone: {
target: "success",
actions: assign({ user: (context, event) => event.data }),
},
onError: {
target: "failure",
actions: assign({ error: (context, event) => event.data }),
},
},
},
success: {},
failure: {
on: {
RETRY: "loading",
},
},
},
});
已解析的数据被放置到’done.invoke.
{
"type": "done.invoke.getUser",
"data": {
"name": "David",
"location": "Florida"
}
}
Promise Rejection
如果一个{ type: 'error.platform' }
事件。错误数据可以在事件的
const search = (context, event) => new Promise((resolve, reject) => {
if (!event.query.length) {
return reject('No query specified');
// or:
// throw new Error('No query specified');
}
return getSearchResults(event.query);
});
// ...
const searchMachine = Machine({
id: 'search',
initial: 'idle',
context: {
results: undefined,
errorMessage: undefined,
},
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
invoke: {
id: 'search'
src: search,
onError: {
target: 'failure',
actions: assign({
errorMessage: (context, event) => {
// event is:
// { type: 'error.platform', data: 'No query specified' }
return event.data;
}
})
},
onDone: {
target: 'success',
actions: assign({ results: (_, event) => event.data })
}
}
},
success: {},
failure: {}
}
});
Invoking Callbacks
发送到父机的事件流可以通过回调处理,这是一个接收两个参数的函数:
// ...
counting: {
invoke: {
id: 'incInterval',
src: (context, event) => (callback, onReceive) => {
// This will send the 'INC' event to the parent every second
const id = setInterval(() => callback('INC'), 1000);
// Perform cleanup
return () => clearInterval(id);
}
},
on: {
INC: { actions: assign({ counter: context => context.counter + 1 }) }
}
}
// ...
Invoking Machines
不同的状态机之间可以按层级调用:
- 父向子可以通过
send(EVENT, { to: 'someChildId' })
- 子向父可以通过
sendParent(EVENT)
如果退出了状态机被调用的状态,状态机就会停止。
import { Machine, interpret, send, sendParent } from "xstate";
// Invoked child machine
const minuteMachine = Machine({
id: "timer",
initial: "active",
states: {
active: {
after: {
60000: "finished",
},
},
finished: { type: "final" },
},
});
const parentMachine = Machine({
id: "parent",
initial: "pending",
states: {
pending: {
invoke: {
src: minuteMachine,
// The onDone transition will be taken when the
// minuteMachine has reached its top-level final state.
onDone: "timesUp",
},
},
timesUp: {
type: "final",
},
},
});
const service = interpret(parentMachine)
.onTransition((state) => console.log(state.value))
.start();
// => 'pending'
// ... after 1 minute
// => 'timesUp'
Invoking with Context
子机可以使用从父机的上下文衍生出的带有数据属性的上下文被调用。例如,下面的{ duration: 3000 }
const timerMachine = Machine({
id: "timer",
context: {
duration: 1000, // default duration
},
/* ... */
});
const parentMachine = Machine({
id: "parent",
initial: "active",
context: {
customDuration: 3000,
},
states: {
active: {
invoke: {
id: "timer",
src: timerMachine,
// Deriving child context from parent context
data: {
duration: (context, event) => context.customDuration,
},
},
},
},
});
就像
// Object (per-property):
data: {
duration: (context, event) => context.customDuration,
foo: (context, event) => event.value,
bar: 'static value'
}
// Function (aggregate), equivalent to above:
data: (context, event) => ({
duration: context.customDuration,
foo: event.value,
bar: 'static value'
})