如何直观的在JavaScript中管理状态

原文:How to visually design state in JavaScriptjavascript

做者:Shawn McKayjava

译者:逆图ios

一份教你使用状态机和状态图来开发应用的路线图git

为何状态管理在JavaScript中显得特别困难?是现代应用继承的复杂性,仍是工具的问题?其余工程领域是如何开发可靠和可预测的系统?是否有可能设计一个系统并将它转换成代码,反之亦然?github

让咱们探索状态管理中的范式转换【译者注:关于范式转换,请戳这里】,用状态机状态图来直观的设计系统。web

概念 > 库

我接触状态管理已经有一段时间了。并且已经尝试过了各类各样的状态管理库:Flux,Reflux,Redux,Dva,Vuex,Mobx以及我本身的库typescript

争论哪个库是更高效的解决方案是没有意义的。各个状态管理库虽然有着不一样的口味但却有着相同的配方。它们都是整个拼图中的一部分————它们让链接和同步数据变得更加容易。编程

接下来咱们所关注的解决方案涉及更大的蓝图:咱们须要在规划和设计系统方面作得更好。redux

打碎一切

想象一个你认为设计优雅的产品。一种可以抵御住用户大量随机交互后果的东西————你懂的,当用户按下按钮的次数超过预期时,这种不可预测性会以某种意想不到的顺序与输入来进行交互,或者是可以以某种其它方式来让你怀疑人生。生活老是很难在正轨上。浏览器

让我来预测下你正在想象的这个产品是什么吧。

嗯……你可能不会考虑专为web而打造的东西,那里的哲学彷佛是"快速行动,破除陈规"【译者注:原文是:'move fast and break things'.这句话被做为标语贴在Facebook的办公室内】。

从更新频率上来看,你可能也没有考虑过手机。

你可能甚至都没想过那些最近才开发的东西。咱们在开发可靠产品方面的能力彷佛在倒退。

我想我知道你在想什么了…

wlakman

我猜对了吗?...也许没有?【译者注:确定是没有...原po多是个索粉...】

你可能不知道,这是20世纪80年代生产的一款的索尼随身听【walkman】。

小时候,我从一位已经升级到使用便携CD播放器的朋友那里收到了一个相似wlakman的卡带播放器。我明白,一些年轻的读者可能对这两种设备都不太熟悉。大家能够把Walkman想象成iPhone,但按键更大,也更容易坏。个人主要任务就是:搞坏它。

我将会尝试全部不一样按钮的使用组合,看看会发生什么:

  • 在磁带快速转动时将它弹出
  • 在快进的同时进行倒带

在尽我所能的各类尝试下,索尼Walkman的表现依旧比今天绝大多数网站都要好。

硬件界面

像Walkman这样的电子设备经受住了用户测试的挑战,没有任何隐藏或禁用用户界面元素的能力。任何按钮均可以随时被按下,任何事情均可能发生。然而它彷佛依旧牢不可破

这让我想知道:

也许电子设备能为咱们如何构建网络界面提供了更好的范例。

咱们能够从古老的电子产品设计过程当中学到什么?咱们如何更好地设计应用?马蒂,咱们须要回到将来!【译者注:出自美国经典科幻电影,《回到将来》.Marty是影片中的男主角.】

电子产品与网络

电子产品真的能教咱们如何更好地在浏览器中建立应用吗?

考虑到组件在过去五年中是Web开发中最大的变化之一。也许咱们能够借用一些电子工程中的其余概念吗?

做为Web开发人员,咱们的条件已经很好了。真的真的很好。发现了一个bug?不要紧,咱们能够在一小时内将更新部署到服务器。

可是其余的工程领域并非那么宽容的。硬件产生问题每每直接致使设备被废弃。嵌入式开发人员必须很是当心,以确保固件更新时不会耗尽电池或者让全部现有设备崩溃。

Web开发者拥有有一种不计后果的奢侈。

更不用说,软件开发者不多会遇到和电子设备开发者同样的资源限制。你最近一次关心的多是性能内存使用,而不是如何让这该死的东西工做。每秒60帧是一道低门槛。但随着咱们开始构建愈来愈复杂的应用且但愿它能在低端手机和物联网设备上运行,这道门槛的标准正在上升。咱们正逐渐面临底层工程师数十年来所经历的工程问题。

约束培育创造力。限制带来了更好的设计。

要了解如何限制会带来更好的设计,咱们须要回到状态管理的基础。

一些状态管理基础

当下社区中的讨论方向每每是倾向NPM包而不是基本的计算机科学原理。

工程师们历来不会问:“哪一个库更好?”他们会问:“咱们该如何设计一个更好的系统?”

咱们能够从一些设计良好的基本原则开始:

  • 区分不肯定数据和有限状态
  • 限制从一个状态到另外一个状态转换的可能性
  • 设计直观

我将经过8个要点来使用本身的方式完成这些任务。

1.状态不等于数据

在程序系统中,状态数据之间的差别是模糊的。他们都保存在内存中,所以被相同对待。

在React中,状态数据共享相同的名字和机制:

  • 获取:this.state
  • 存储:this.state = {}
  • 更新:this.setState(nextState)

在电子产品中,对状态数据的区别就没有那么多的混淆。

状态表示系统能够处于有限数量的模式下————一般由电路自己定义。对Walkman来讲,就像是“Playing”,“Stopped”,“Ejected”。就像是一种“模式”或“配置”,状态是可数的。

数据存储在具备几乎有无限可能的存储器中。对Walkman来讲,就像是正在演奏的歌曲“Song 2”。数据,就像歌曲,拥有无限的可能性。

不管下面的DataLoader组件作什么,它的状态都只可能生成一组有限的视图:“loading”,“loaded”,“error”。

分离状态和数据能够减小混淆,并容许咱们构建基于有限状态机的应用。

2.状态有限

电子产品开发者很早就知道,一个可预测的界面必定是具备有限和可控状态数量的状态。若是状态的数量不加控制,系统不只会难以调试,更没法进行完全的测试。

在有限状态机中,状态是被明肯定义的。转换是一组你能够进行状态转移事件的集合。

举个栗子,使用事件“STOP”会触发状态转换将状态转移至“Stopped”。

在React中,咱们能够定义一个拥有两种状态的基本款Walkman:“Stopped”和“Playing”。

能够在这个CodeSandBox中查看具体演示。

在一个有限状态机中,系统始终会处于一个可能的配置下。这个界面绝无可能出现除了“Playing”和“Stopped”以外的东西。只要对这两种状态进行测试就可以让咱们对系统充满信心!

3.管理状态机的复杂度

让咱们看看当咱们开始向这个状态机样例中添加两个新状态时会发生什么:“Rewinding”和“FastForwarding”。

当状态相同时,他们看起来是很容易添加的。每个状态都像是一个能够单独单独开发和测试的模块。不过要当心,状态转换并不老是可行的。

咱们应该注意不一样状态间不受控制的转换

也许被你发现了。咱们在上面的代码里引入了一个bug。花点时间去看看你可否发现其中的问题。

4.守卫转换

因为咱们容许用户在rewindingfastForwarding两种状态间快速切换,并且没有在切换过程当中去将磁带暂停。咱们的磁带彷佛缠成了一坨。

为了解决这个问题,咱们能够为咱们的状态转换添加一层守卫。守卫是状态转换发生所必需要知足的条件。例如:咱们能够确保事件FASTFORWARD,REWIND,和PLAY只能在状态为“Stopped”时触发。

除非咱们从新思考咱们状态管理的规划和设计方式,不然意外的状态转换是必然发生的。

当咱们添加诸如ejected这样的额外状态时,咱们必须去思考哪一种状态转换在哪一种条件下是被容许的。拿Walkman来讲,你能够在它处于中止模式时按下中止键来弹出磁带。为了添加这个功能,咱们必须添加更多的守卫,并肯定哪些转换是可行的。

伴随着各类新状态的添加,各类未处理状态组合的可能性成倍的增长。这并非一个可拓展的解决方案。每一个额外状态都会检查全部的转换守卫。

这开始变得像是状态在管理你了。

管理守卫的问题来源于状态的表现方式:“Stopped”,“Playing”,“Rewinding”。

状态的理想数据结构并非字符串或对象。

但那又会是什么呢?

5.状态是图

状态理想的数据结构一般是图。状态图提供了一种直观的方法来设计,可视化的控制状态在每一个节点间的转换。

这并非什么大新闻————电子工程师们这几十年来一直在用状态图来描述复杂系统。

咱们来看一个网上的例子。AWS Step Function为绘制应用程序工做流提供了可视化界面。每一个节点控制一个lambda————一个在云上调用的远程函数————每一个函数的输出会触发下一个函数的输入。

AWS Step Functions

在上面的例子中,咱们能够很清楚的看到用户的操做是如何在每一个步骤中移动的。包括那些可能的错误以及错误处理。添加额外的步骤并不会致使复杂性的指数级增长。

一些工程师可能已经注意到了,Step Function与PLC(可编程逻辑控制器)模块接线图有很多共同之处。一些设计师可能会说这与他们的工做流程图也有不少共同点。咱们设计状态的方式难道不该该与设计应用的方式有更多的共同之处吗?

6.状态图脚手架

状态图应当成为开发应用的脚手架。

举个例子。经过状态图,咱们能够更加容易直观的理解Walkman的操做。

Walkman 状态图

若是没有深刻研究一些较为复杂的“守卫”代码,咱们确定会说除非在“Stopped”状态下,不然都不该该从“Rewinding”跳到任何其它状态。与此相反的是,你应该列出你的应用能够作的全部转换,而不是概述的说它不能作什么事。开发方式从防护性的自下向上的编码转为自顶向下的设计。这种转换是事半功倍的。

状态图更直观,更容易调试,并且更容易容纳新需求。经过状态机,每种状态的变化都可以与它相邻的状态相隔离。更不用说那些复杂的状态“守卫”逻辑能够经过一种更加直观易理解的方式去涵盖。

不幸的是,状态图也多是一颗定时炸弹

重度连通图是无法缩放的。想一想若是咱们在上图中再添加另外4个状态会发生什么。可读性下降并且重复性增长,各个纠缠的箭头指向各个方向去争夺空间。这种状态图的泛化被称为状态爆炸

幸运的是,有一种使用形式化描述系统的方式能够用来减轻状态图的复杂度:让咱们开始探索statecharts

7.掌握statecharts

我第一次了解到statecharts是在Luca Matteis在温哥华React直面会上所作的《如何使用statecharts为Redux应用的行为建模》演讲。次日在工做中,我提出了“新”的状态管理模式,其实我只是发现了许多我身边的工程师早已熟悉的概念。我在一家基于IOT的公司工做,与许多硬件和嵌入式开发的工程师一块儿工做。咱们正在招聘:)。

statechart的概念能够追溯到1987年,当时数学家David Harel发表了一篇关于可视化描述复杂系统的论文。就如下面的石英表为例:

一旦你理解了它自身的语言,statechart既直观又容易掌握。

上面的例子介绍了一些新的状态类型:

  • 初始状态 ———— 由带箭头的点标记的起始状态。
  • 嵌套状态 ———— 能够访问其父级转换的状态。
  • 并行状态 ———— 由虚线表示的两个平行状态。
  • 历史状态 ———— 记住并能够返回其先前值的状态。

除此以外,statecharts还能够包含触发转换行为的时间和方式:

  • 转换 ———— 一个基于命名事件触发状态更改的函数。“Stopped”→转换(“Play”)→“Playing”
  • 守卫 ———— 转换发生必须知足的条件。例如,若是没有磁带,或者磁带已经播到了最后,则没法触发“play”。“Stopped”→转换(“Play”)**[hasTape]**→“Playing”。在既定的顺序下,是能够产生屡次转换的。
  • 行为 ———— 基于状态变化发生的触发器。例如,当状态进入“playing”时触发磁带播放。动做可能发生在onEntryonExit上。

将Walkman示例用statechart重写能够删除状态图中冗余的部分。注意,如今再也不须要重复“STOP”事件了。statechart是可扩展的————添加其余并行状态(如“Recording”和“Volume”)并不难。

Statechart并不只仅是一个可视化描述应用程序的概念。

statechart能够为咱们生成应用程序底层的状态机

你能够将看到的图转化为代码,反之亦然。以图表的形式来检视你的应用逻辑,或者把它画出来。

8.statecharts工具

Statecharts为真正设计系统提供了一个充满但愿的将来————而不只仅是纸上谈兵。虽然其余编程语言已经出现了相关工具,但JavaScript如今才刚刚开始显现出statechart工具的繁荣。

C和Java的开发人员能够经过使用工具来编写statechart进行编码。做为一个例子,Yakindu Statechart Tools中聚集了各类可视化设计和代码。我最近学会了Yakindu还作了一个Typescript代码生成器

一样的工具最终也能够用于JavaScript。

Sketch Systems提供了一种在markdown中设计系统的方法。这能够用于在JavaScript中设计原型。虽然Sketch Systems尚不支持行为守卫,但我发现它对于原型设计和测试状态图都很是有用。

Sketch Systems容许你将图表导出到XState,这是一个基于statechart的JavaScript库,具备本身的可视化和可点击状态原型设计工具。

想象一下你编辑器中更高级的工具。想象一下你的工做流程,你能够在可视化设计和手动编写应用程序逻辑之间无缝切换。在社区中来推进咱们想要更好地支持使用statechart的工具,库和编辑器插件,咱们所必须付出的巨大努力是值得的。

结论

复杂度已经悄然降临到了JavaScript社区中了。我认为咱们尚未作好准备。就我的而言,我认可我花了很长时间才开始擅长设计应用。我会先画出一个组件树和一些状态的草稿。看着原型图一步步迭代进生产。可是,若是没有一个可视化规格语言来设计状态图,我怎能真正擅长设计应用程序呢?

在很长一段时间里,我坦白我接近状态管理更多的是仅仅是好奇它的高深莫测。我不知道从计算机科学的其余领域也能够学到不少东西,有更长的创建和管理复杂系统的历史。我逐渐明白,回顾过去是有价值的,而且要多关注咱们周围的工程领域。

咱们能够向那些开创并开发了数十年系统解决方案的工程师学习,这些解决方案可用于建立复杂但可预测的系统。咱们能够在工具和库的基础上构建一个生态系统,以支持应用程序逻辑的可视化设计。咱们须要这么作,由于JavaScript须要有这些东西。

在JavaScript中设计应用程序拥有比以往都更加光明的将来。这篇文章站在很是高的角度上,可能留下的问题多于答案。在第2部分中,我想更深刻地讨论使用statecharts构建组件的模式。

相关文章
相关标签/搜索