我过去写过关于有限状态机的文章 ,并提到XState 。 在这篇文章中,我想介绍这个流行JavaScript库。
有限状态机是一种有趣的方式,可以解决复杂的状态和状态更改,并尽可能避免代码错误。
正如我们使用各种工具为软件项目建模以帮助我们在构建软件之前对其进行设计,以及使用模型和UX工具在构建UI之前考虑UI一样,有限状态机可以帮助我们解决状态转换。
计算机程序都是关于输入后从一种状态过渡到另一种状态的。 如果您不密切注意,事情可能会失控,并且XState是一个非常有用的工具,可帮助我们应对状态复杂性的增长。
您使用npm安装XState:
npm install xstate
然后您可以使用ES Modules语法将其导入程序中。 通常,至少需要导入Machine
并interpret
功能:
import { Machine, interpret } from 'xstate'
在浏览器中,您也可以直接从CDN导入它:
<script src="https://unpkg.com/xstate@4/dist/xstate.js"></script>
这将在window
对象上创建一个全局XState变量。
接下来,您可以使用Machine
factory功能定义一个有限状态机。 此函数接受配置对象,并返回对新创建的状态机的引用:
const machine = Machine({
})
在配置中,我们传递一个标识状态机的id
字符串,即初始状态字符串。 这是一个简单的交通灯示例:
const machine = Machine({ id: 'trafficlights', initial: 'green' })
我们还传递了一个包含允许状态的states
对象:
const machine = Machine({ id: 'trafficlights', initial: 'green', states: { green: { }, yellow: { }, red: { } } })
在这里,我定义了3种状态: green
yellow
和red
。
为了从一种状态过渡到另一种状态,我们将向计算机发送一条消息,并且它将根据我们设置的配置知道要做什么。
在这里,我们设置为在green
状态时切换到yellow
状态,并得到TIMER
事件:
const machine = Machine({ id: 'trafficlights', initial: 'green', states: { green: { on: { TIMER: 'yellow' } }, yellow: { }, red: { } } })
我将其称为TIMER
是因为交通信号灯通常具有一个简单的计时器,该计时器每X秒更改一次灯光状态。
现在让我们填充其他两个状态转换:我们从黄色到红色,从红色到绿色:
const machine = Machine({ id: 'trafficlights', initial: 'green', states: { green: { on: { TIMER: 'yellow' } }, yellow: { on: { TIMER: 'red' } }, red: { on: { TIMER: 'green' } } } })
我们如何触发过渡?
您可以使用以下命令获取机器的初始状态字符串表示形式:
machine.initialState.value //'green' in our case
我们可以使用切换到新的状态transition()
的方法machine
(由返回的状态机实例Machine()
const currentState = machine.initialState.value
const newState = machine.transition(currentState, 'TIMER')
您可以将新的状态对象存储到变量中,并可以通过访问value
属性来获取其字符串表示形式:
const currentState = machine.initialState.value const newState = machine.transition(currentState, 'TIMER') console.log(newState.value)
使用transition()
方法,您始终必须跟踪当前状态,在我看来这会带来一些麻烦。 如果我们可以询问机器当前状态,那就太好了。
这是通过创建状态图完成的,该状态图在XState中称为服务。 xstate
,我们调用从xstate
导入的interpret()
方法, xstate
传递给状态机对象,然后调用start()
来启动服务:
const toggleService = interpret(machine).start()
现在,我们可以使用此服务的send()
方法来检索新状态,而不必像使用machine.transition()
那样传递当前状态:
const toggleService = interpret(machine).start()
toggleService.send('TOGGLE')
我们可以存储返回值,该值将保持新状态:
const newState = toggleService.send('TOGGLE')
console.log(newState.value)
这仅仅是XState的表面。
给定一个状态,您可以使用其nextEvents
属性知道什么将触发状态更改,该属性将返回一个数组。
是的,因为您可以根据触发条件从一个状态进入多个状态。
对于交通信号灯,这不会发生,但是让我们对有限状态机中的房屋信号灯示例进行建模:
进入房屋时,您可以按两个按钮之一p1或p2。 当您按这些按钮中的任何一个时,l1指示灯将点亮。
想象这是入口灯,您可以脱下外套。 完成后,您可以决定要进入哪个房间(例如厨房或卧室)。
如果按按钮p1,则l1关闭,l2打开。 相反,如果您按下按钮p2,则l1关闭,l3打开。
再次按下两个按钮p1或p2中的任何一个,当前点亮的灯将熄灭,我们将回到系统的初始状态。
这是我们的XState机器对象:
const machine = Machine({ id: 'roomlights', initial: 'nolights', states: { nolights: { on: { p1: 'l1', p2: 'l1' } }, l1: { on: { p1: 'l2', p2: 'l3' } }, l2: { on: { p1: 'nolights', p2: 'nolights' } }, l3: { on: { p1: 'nolights', p2: 'nolights' } }, } })
现在我们可以创建服务并向其发送消息:
const toggleService = interpret(machine).start(); toggleService.send('p1').value //'l1' toggleService.send('p1').value //'l2' toggleService.send('p1').value //'nolights'
我们在这里想念的一件事是,当我们切换到新状态时我们该如何做 。 这是通过操作完成的,我们在第二个对象参数中定义了这些操作,然后将其传递给Machine()
工厂函数:
const machine = Machine({ id: 'roomlights', initial: 'nolights', states: { nolights: { on: { p1: { target: 'l1', actions: 'turnOnL1' }, p2: { target: 'l1', actions: 'turnOnL1' } } }, l1: { on: { p1: { target: 'l2', actions: 'turnOnL2' }, p2: { target: 'l3', actions: 'turnOnL3' } } }, l2: { on: { p1: { target: 'nolights', actions: ['turnOffAll'] }, p2: { target: 'nolights', actions: ['turnOffAll'] } } }, l3: { on: { p1: { target: 'nolights', actions: 'turnOffAll' }, p2: { target: 'nolights', actions: 'turnOffAll' } } }, } }, { actions: { turnOnL1: (context, event) => { console.log('turnOnL1') }, turnOnL2: (context, event) => { console.log('turnOnL2') }, turnOnL3: (context, event) => { console.log('turnOnL3') }, turnOffAll: (context, event) => { console.log('turnOffAll') } } })
看看现在如何将对象中定义的每个状态转换传递给对象on
而不仅仅是一个字符串,它是一个具有target
属性的对象(我们传递之前使用的字符串),还有一个actions
属性,可以将action设置为跑。
我们可以通过传递字符串数组而不是字符串来运行多个动作。
您还可以直接在actions
属性上定义动作,而不是将它们“集中”到一个单独的对象中:
const machine = Machine({ id: 'roomlights', initial: 'nolights', states: { nolights: { on: { p1: { target: 'l1', actions: (context, event) => { console.log('turnOnL1') }, ...
但是在这种情况下,将它们放在一起很方便,因为不同的状态转换会触发相似的动作。
本教程就是这样。 我建议您查看XState Docs以获取XState的更高级用法,但这只是一个开始。
喜欢这篇文章?欢迎打赏~~