Context & Actions
Context & Actions
一个
const machine = Machine({
context: {
// 资料 (data) 存在 context 裡,key 可以自己订
count: 0,
user: null,
},
states: {
//...
},
});
我们可以透过
const myMachine = machine.withContext({
count: 10,
user: {
name: "Jerry",
},
});
在任何状态下,我们都可以拿到
machine.initialState.context;
// { user: null, count: 0 }
const service = interpret(machine.withContext({
count: 10,
user: {
name: 'Jerry'
},
});
service.start();
service.state.context;
// { user: { name: 'Jerry' }, count: 10 }
至于要如何在特定的状态中改变
Effects
在
Fire-and-forget effects - 指执行Side Effect 后不会另外送任何event 回statechart 的effect 。Invoked effects - 指除了可执行Side Effect 之外还能发送和接收events 的effect 。
这两类
- Fire-and-forget effects
Actions - 用于单次、离散的Effect Activities - 用于连续的Effect
- Invoked effects
- Invoked Promises
- Invoked Callbacks
- Invoked Observables
- invoked Machines
Actions
const action = (context, event, actionMeta) => {
// do something...
};
我们可以把
const lightMachine = Machine({
initial: "red",
states: {
red: {
on: {
CLICK: {
// 转换到 green 的状态
target: "green",
// transition actions
actions: (context, event) => console.log("hello green"),
},
},
},
green: {
on: {
CLICK: {
target: "red",
// transition actions
actions: (context, event) => console.log("hello red"),
},
},
},
},
});
另外还有两种
const lightMachine = Machine({
initial: "red",
states: {
red: {
// entry actions
entry: (context, event) => console.log("entry red"),
// exit actions
exit: (context, event) => console.log("exit red"),
on: {
CLICK: {
target: "green",
},
},
},
//...
},
});
在进入
const lightMachine = Machine({
initial: 'red',
states: {
red: {
// entry actions
entry: 'entryRed'
// exit actions
exit: 'exitRed',
on: {
CLICK: {
target: 'green',
// transition actions
actions: 'redClick',
},
}
},
//...
}
}, {
actions: {
entryRed: (context, event) => console.log('entry red'),
exitRed: (context, event) => console.log('exit red'),
redClick: (context, event) => console.log('hello green'),
},
});
所有设定
const lightMachine = Machine(
{
initial: "red",
states: {
red: {
// entry actions
entry: ["entryRed", "temp"],
// exit actions
exit: ["exitRed", "temp"],
on: {
CLICK: {
target: "green",
// transition actions
actions: ["redClick", "temp"],
},
},
},
//...
},
},
{
actions: {
entryRed: (context, event) => console.log("entry red"),
exitRed: (context, event) => console.log("exit red"),
redClick: (context, event) => console.log("hello green"),
temp: (context, event) => console.log("temp"),
},
}
);
在实务开发上,不建议直接把
CLICK: {
target: 'gerrn',
actions: (context, event) => console.log('hello green')
}
建议统一把
const lightMachine = Machine(
{
initial: "red",
states: {
red: {
// entry actions
entry: ["entryRed", "temp"],
//...
},
//...
},
},
{
actions: {
entryRed: (context, event) => console.log("entry red"),
temp: (context, event) => console.log("temp"),
},
}
);
Assign Action
import { Machine, assign } from "xstate";
// ...
actions: assign({
// 透过外部传进来的 event 来改变 count
count: (context, event) => context.count + event.value,
message: "value 也可以直接是 static value",
});
// ...
// ...
// 他会 partial update context
actions: assign((context, event) => {
return {
count: context.count + event.value,
message: 'value 也可以直接是 static value'
}
}),
// ...
让我们直接来看一个简单的例子吧:
const counterMachine = Machine(
{
id: "counter",
initial: "ENABLED",
context: {
count: 0,
},
states: {
ENABLED: {
on: {
INC: {
actions: ["increment"],
},
DYNAMIC_INC: {
actions: ["dynamic_increment"],
},
RESET: {
actions: ["reset"],
},
DISABLE: "DISABLED",
},
},
DISABLED: {
on: {
ENABLE: "ENABLED",
},
},
},
},
{
actions: {
increment: assign({
count: (context) => context.count + 1,
}),
dynamic_increment: assign({
count: (context, event) => context.count + (event.value || 0),
}),
reset: assign({
count: 0,
}),
},
}
);
从上面这个范例,可以看出使用
//...
on: {
[COUNTER_EVENTS.DYNAMIC_INC]: {
actions: ['dynamic_increment'],
},
}
//...
actions: {
dynamic_increment: assign({
count: (context, event) => context.count + (event.value || 0)
// event 除了 type 这个属性之外有什麽 property 是外部决定的
}),
},
//...
//...
<Button
label="Increment"
onClick={() =>
// 这裡传入 DYNAMIC_INC event 同时要给 value
send({ type: COUNTER_EVENTS.DYNAMIC_INC, value: Number(value) })
}
/>
//...
注意事项
- 永远不要从外部修改一个
machine 内的context ,任何改变context 的行为都应该来自event 。 - 推荐使用
assign({ ... })
的写法,这个写法利于未来的工具做分析。 - 跟所有
actions 相同不建议inline 写在machine 裡面,建议定义在machine options 的actions 内。 - 理想上,
context 应该是一个JS 的plain object ,并且应该可以被序列化。 - 记得
assign
就只是pure function 回传一个action 物件,并直接对machine 造成影响。