// ...
command(nextState, action) {
switch (nextState) {
case 'loading':
// execute the search command
this.search(action.query);
break;
case 'gallery':
if (action.items) {
// update the state with the found items
return { items: action.items };
}
break;
case 'photo':
if (action.item) {
// update the state with the selected photo item
return { photo: action.item };
}
break;
default:
break;
}
}
// ...
用户界面可以表达为以下两个方面:
从信用卡支付设备、气泵屏幕到你们公司创建的软件,用户界面都会对用户和其他来源的动作做出反应,并相应地更改其状态。
这个概念不仅限于技术,它是一切事物运作方式的基本组成部分:
这是一个可以帮助我们开发更好的用户界面的概念,但在我们开始前,我希望你尝试一些东西。
例如一个照片相册库,它的用户交互流程如下:
现在想想如何开发它。
也许可以尝试用React来编写它。
我会等! 我只是一篇文章。
我不会去任何地方。
完了吗?真棒! 那不是太难,对吧?
现在考虑以下您可能忘记的场景:
这些只是我们在规划、开发或测试过程中可能出现的一些潜在问题。
在软件开发中,几乎没有什么比你认为已经涵盖所有可能的用例更糟糕的事情了,当你发现(或接收)新的边缘情况时,一旦您考虑它们,将进一步使代码复杂化。
我们通常难以介入一些已经存在的项目,它们所有的这些用例都没有文档化,反而是隐藏在杂乱的代码中,留给你解读。
显而易见
如果我们可以确定所有可能的UI状态,这些状态来自作用在每个状态上的所有可能动作,该怎么办?
如果我们能够可视化这些状态、动作和状态之间的转换,怎么办?
设计师直观地做到了这一点,即所谓的“用户流程”(或“UX流程”),根据用户交互描述UI的下一个状态应该是什么。
在计算机科学术语中,有一个计算模型,称为有限自动机或“有限状态机”(FSM),它可以表达相同类型的信息。
也就是说,它们描述了对当前状态执行动作时的下一个状态。
就像用户流程一样,这些有限状态机可以清晰明确地可视化。
例如,以下是描述交通信号灯FSM的状态转换图:
什么是有限状态机
状态机是对应用程序中的行为进行建模的有用方法:对于每个动作,都会以状态更改的形式进行反应。
经典有限状态机有5个部分:
idle
、loading
、success
、error
等)SEARCH
、CANCEL
、SELECT_PHONE
等)idle
)transition('idle', 'SEARCH') === 'loading'
)确定性的有限状态机(我们将要处理的)也有一些约束:
currentState
和action
,转换函数必须始终返回相同的nextState
(这是“确定性”部分)描述有限状态机
一个有限状态机可以表示为从状态到其“转换”的映射,其中每个转换是一个动作和随动作而产生的下一个状态,这个映射只是一个普通的JavaScript对象。
让我们来看一个美国红绿灯的例子,这是最简单的FSM示例之一。
假设我们从
green
开始,然后在一些TIMER
后,转换为yellow
,然后在另一个TIMER
后,转换为red
,然后在另一个TIMER
之后转回green
:一个 转换函数 回答了以下问题:
在以上的代码示例中,基于一个动作(这里是
TIMER
),转换到下一个状态,只是在machine
对象中查找currentState
和action
,因为:machine[currentState]
得到下一个动作的映射,例如machine['green'] == {TIMER: 'yellow'}
machine[currentState][action]
得到来自该动作的下一个状态,例如machine['green']['TIMER'] == 'yellow'
:而不是使用
if / else
或switch
语句来确定下一个状态,例如,if (currentState ===='green') return 'yellow'
。我们将所有逻辑移动到一个可以序列化为JSON的普通JavaScript对象中。
这是一种可以在测试、可视化、重用、分析、灵活性以及可配置性等方面获得巨大回报的策略。
React中的有限状态机
请看一个更复杂的例子,让我们看看我们是如何使用有限状态机来表示我们的图库应用程序。
该应用程序可以处于以下几种状态之一:
start
- 初始的搜索页面loading
- 搜索结果加载页面error
- 搜索失败页面gallery
- 搜索成功页面photo
- 单个照片详情页然后可以由用户或应用程序本身执行多个动作:
SEARCH
- 用户单击了“搜索”按钮SEARCH_SUCCESS
- 成功搜索到请求的图片SEARCH_FAILURE
- 由于某个错误,搜索失败CANCEL_SEARCH
- 用户点击了“取消搜索”按钮SELECT_PHOTO
- 用户单击了相册中的某张图片EXIT_PHOTO
- 用户退出了图片详情页最初,可视化地将这些状态和动作结合在一起的最佳方法就是使用两种非常强大的工具:铅笔和纸;在状态之间绘制箭头,并将导致状态转移的动作标记在箭头上。
我们现在可以在对象中表示这些转换,就像在交通灯示例中一样:
现在让我们看看如何将这种有限状态机配置和转换功能应用到我们的图库应用程序中吧。
在App的组件状态中,将有一个属性指示当前的有限状态,
gallery
:转换函数将会是App类的一个方法,以便我们可以检索当前的有限状态:
这看起来和之前描述的
transition(currentState, action)
函数类似,但有一些区别:action
是一个对象,有一个type
属性,标明了动作类型,例如type: 'SEARCH'
。action
,我们才能从this.state.gallery
中检索出当前的有限状态。nextGalleryState
)以及一些产生自一个命令的扩展状态(nextState
)所更新(详见 执行命令 )。执行命令
当状态发生变化时,可能会执行“副作用”(或“命令”,因为我们将引用它们)。
例如,当用户单击“搜索”按钮并发出“搜索”动作时,状态将转换为
loading
,然后会向Flickr发起一个异步的搜索请求(否则,loading
将是谎言,开发者永远都不应该撒谎)。我们可以在
command(nextState,action)
方法中处理这些副作用,它对于给定的下一个有限状态和动作的有效负载,可以确定要执行什么,以及扩展状态应该是什么:动作可以拥有除了动作类型之外的有效负载,它可能需要被用来更新应用程序状态, 例如,当
SEARCH
操作成功时,可以在负载中带上搜索结果中的items
,发出一个SEARCH_SUCCESS
操作:上面的
command
方法将立即返回任何扩展状态(即有限状态以外的状态),this.state
将随着有限状态的变化,被setState(...)
所更新。最终的状态机应用
由于我们已经为应用程序声明性地配置了有限状态机,因此我们可以基于当前有限状态的有条件渲染,以更清晰的方式呈现正确的UI:
CSS中的有限状态
您可能已经注意到上面代码中的
data-state = {galleryState}
。通过设置该
data-attribute
,我们可以使用属性选择器有条件地设置应用程序的任何部分:这比使用
className
更好,因为您可以强制执行约束,即一次只能为数据状态设置一个值,同时还可以取得和className
一样的效果。大多数流行的CSS-in-JS解决方案也支持属性选择器。
优势
使用有限状态机来描述复杂应用程序的行为并不是什么新鲜事。
传统上,这是通过
switch
和goto
语句完成的,但通过将有限状态机描述为状态、操作和下一状态之间的声明性映射,您可以使用该数据来可视化状态转换过程:此外,使用声明性有限状态机允许您:
结论和题外话
有限状态机是用于对应用程序中可以表示为有限状态的部分进行建模的抽象,几乎所有应用程序都具有这些部分。
本文中介绍的FSM编码模式:
从现在开始,当遇到“布尔标志”的变量(如
isLoaded
或isSuccess
)时,我建议您停下来思考如何将应用程序状态建模为有限状态机。这样,您可以重构您的应用程序,以状态表示
state === 'loaded' 或
state === 'success'`,使用枚举状态代替布尔标志。资源