有限状态机
有限状态机
有限状态机 (Finite State Machine, FSM) 是一种数学模型用来描述系统的行为,这个系统在任何时间点上都只会存在于一个状态。举例来说,红绿灯就有 红灯、绿灯、黄灯 三种状态,在任何时间点上一定是这三种状态的其中一种,不可能在一个时间点上存在两种或两种以上的状态。
一个正式的有限状态机包含五个部分:
- 有限数量的状态 (state)
- 有限数量的事件 (event)
- 一个初始状态 (initial state)
- 一个转换函式 (transition function),传入当前的状态及事件时会返回下一个状态
- 具有 0 至 n 个最终状态 (final state)
需要强调的是这裡的 状态 (State) 指的是系统定性的 mode 或 status,并不是指系统内所有的资料。举例来说,水有 4 种状态 (State)-固态、液态、气态 以及等离子态,这就属于状态,但水的温度是可变的定量且无限的可能就不属于状态!
建立第一个 Machine
XState 的 Machine 其实就是一个 State Machine (精确地说是 Statechart),所以我们在建立一个 Machine 要先整理我们的,程序有哪些状态,哪些事件,以及初始状态。
import { Machine } from "xstate";
const lightMachine = Machine({
states: {
red: {},
green: {},
yellow: {},
},
});
首先我们需要订定 Machine 会有哪些状态,传给 Machine 一个 object 内部必须有 states 这个属性,而 states object 的每个 key 就是这个 Machine 拥有的状态。所以这段,程序码代表这个 Machine 拥有 red, green, yellow 三种状态。
import { Machine } from "xstate";
const lightMachine = Machine({
states: {
red: {},
green: {},
yellow: {},
},
});
接下来我们要定义初始状态,假如说我们希望一开始是红灯,那就给 initial 如下:
import { Machine } from "xstate";
const lightMachine = Machine({
initial: "red",
states: {
red: {},
green: {},
yellow: {},
},
});
initial 给 ‘red’ 这样我们的 lightMachine 的初始状态就会是 red。接下来我们要定义每个状态下会有什麽事件,遇到这些事件时,会转换成什麽状态。这裡我们订定三个状态下都会有 CLICK 事件,并且状态的转换是 red -> green -> yellow -> red … 那我们的,程序码就会像下这面这样:
import { Machine } from "xstate";
const lightMachine = Machine({
initial: "red",
states: {
red: {
on: {
CLICK: "green",
},
},
green: {
on: {
CLICK: "yellow",
},
},
yellow: {
on: {
CLICK: "red",
},
},
},
});
我们在每个状态下加入 on 属性,on 的 key 代表事件名称,value 则代表转移的下一个状态。这时候我们就可以拿 lightMachine 来使用了!透过 .transition(state, event) 这个方法来取得下一个状态:
import { Machine } from "xstate";
const lightMachine = Machine({
//...
});
const state0 = lightMachine.initialState;
console.log(state0);
const state1 = lightMachine.transition(state0, "CLICK");
console.log(state1);
const state2 = lightMachine.transition(state1, "CLICK");
console.log(state2);
const state3 = lightMachine.transition(state2, "CLICK");
console.log(state3);
这个回传的 state object 有两个常用的方法及属性分别是:
- value
- matches(parentStateValue)
- nextEvents
value 可以拿到当前的状态,matches 则可以用来判断现在是否在某个状态,比如说:
import { Machine } from "xstate";
const lightMachine = Machine({
//...
});
const state0 = lightMachine.initialState;
console.log(state0.value); // 'red'
const state1 = lightMachine.transition(state0, "CLICK");
console.log(state1.value); // 'green'
state0.matches("red"); // true
state0.matches("yellow"); // false
state0.matches("green"); // false
nextEvents 则可以拿到该 state 有哪些 events 可以使用:
import { Machine } from "xstate";
const lightMachine = Machine({
//...
});
const state0 = lightMachine.initialState;
console.log(state0.nextEvents); // 'CLICK'
这样一来我们就完成了一个简单的 Machine,但我们的 lightMachine 每次都要传入当前的 state 跟 event 才能做状态转换,这是为了让 transition 保持是一个 Pure Function,它不会改变 lightMachine 物件的状态,方便我们做单元测试。但我们通常不想要自己储存及管理状态,所以 XState 提供了 Interpret!
Interpret
XState 提供了一个叫 interpret 的 function 可以把一个 machine 实例转换成一个具有状态的 service,如下:
import { Machine, interpret } from "xstate";
const lightMachine = Machine({
//...
});
const service = interpret(lightMachine);
// 启动 service
service.start();
// Send events
service.send("CLICK");
// 停止 service 当你不在使用它
service.stop();
interpret 得到的 service 具有自己的状态,当 start() 后,这个 service 就会到初始状态,同时可以对他传送(send)事件,同时也可以透过 service.state 拿到当前的状态,如下
import { Machine, interpret } from "xstate";
const lightMachine = Machine({
//...
});
const service = interpret(lightMachine);
// 启动 service
service.start();
console.log(service.state.value); // 'red'
service.send("CLICK"); // Send events
console.log(service.state.value); // 'green'
// 停止 service 当你不在使用它
service.stop();
这样一来我们就可以很简单的透过 service 来管理及保存当前的状态!