上一篇:从Promise的实现来看有限状态机javascript
上一篇讲到了一个简单的,利用了有限状态机的前端实现Promise
。Promise
的有限状态机除了start以及finish两个状态,其核心的三个状态其实就是一个异步行为的三种状态:PENDING
、FULFILLED
、REJECTED
。经过异步行为的状态转移,Promise
提供了一种将嵌套回调函数的调用方式尽可能地扁平化,造成了一个链式的异步操做到同步操做的状态转换。前端
除了Promise
,前端还有不少的方案是基于有限状态机数学模型来进行实现的。此次来结合实际业务,稍微聊聊目前前端数据状态管理的最热门的方案Redux
吧。java
Redux
和Flux
说到Redux
,就不得不提及Flux
。Flux说到底仍是一种数据管理的理论,而且有不少种的实现。通常见到的都是facebook推广的实现方案。Flux理论的核心就是单向数据流,也就是全部的前端数据都是单向流动的。后端
前端的数据改变,通常都是由一些用户操做触发的。当某个用户操做,触发了某种状态的改变。像蝴蝶效应同样,又引发了其余组件或者模块的状态改变;或者是触发了某些数据请求,在异步的请求完成以后,新的数据被返回到前端,前端根据这些新的数据,进行页面状态的改变。api
Flux将这整个过程拆分为几个部分:前端框架
这四个因素造成一个数据或者说是状态流动的闭环,而且状态在这个闭环中流动都是单向的。架构
而Redux
和Flux的数据流过程基本一致,二者最大的不一样在于Redux依赖Reducer
来进行实际上的状态修改,经过一个pure function来返回一个新的状态,而且和旧的状态进行合并,来触发View的从新渲染。app
这个状态转移过程是否是和上篇文章中说的有限状态机的思想有点相似呢?是的,这里的数据就是状态机中的状态。框架
React
和Redux
做为目前最为热门的MVC前端框架,React自己具备多少优雅的特性就再也不赘言了。繁琐的状态管理可能让不少人在进行React开发的时候,须要很长时间来进行组件的拆分以及重构。异步
在业务环境中,没有人可以在一开始就设计一个完美的组件树结构,尤为是对于较大型的业务页面。随着需求的修改(。。。),随着后端的接口的变化(。。。。。。。),你开始设计出来的组件结构以及状态老是出现漏洞,而后修修补补提测了,上线了。当你进行二次开发,或者新的迭代的时候,你就会发现本来的组件结构让你想死的心都有了,而后不停地重构,再重构。
React全部组件的变化都依赖state
以及props
两个对象,一个来自于外部,一个是自驱动发生改变的。
其实React的state
和props
也是有限状态机中的状态,而每一个具备状态的React组件,就是一个独立的状态机。
Redux为React提供了一种将分布在各个组件中的state
进行统一管理的思想或者说是工具。
可是Redux存在不少问题,其中最为显著的就是若是将整个页面全部的可变状态收集到一个store
中保存,这个store
可能会变得很庞大,某些无用的状态致使store
中存在不少冗余。
React Redux
的复杂业务逻辑先描述一个业务场景:咱们须要一个很长的活动页面,这个页面中有展现、评论、点赞、抽奖、试听等多个模块,首屏数据经过batch
从服务端获取,以后的每次数据请求都是经过单独的接口进行的。
上面的这些模块,有哪些须要经过状态机的模型来进行状态管理呢?
通常来讲,无状态的展现组件是不须要进行状态管理的,因此展现、试听这种模块是不须要的。
其次,点赞是一个布尔的变化,只有成功和失败两种状态,也不须要咱们如此费心去管理状态。
那么评论可能争议很大,看似比较复杂的模块其实并无太多的逻辑,可能评论区域显示的内容不少,但大多都是静态内容,也没有太多的状态改变,而且评论部分数据量比较大,若是采用Redux会致使store
的结构不容易扁平化,形成组件性能损失。
剩下的就是抽奖了,抽奖的状态很是多,比较难以管理,而且数据内容较少,很适合进行集中式地状态管理,随着业务的迭代,抽奖可能会发展出多种抽奖条件,这样修改源代码的难度也会较大。整个业务逻辑可能就变成了下面这样。
为了让你们可以理解抽奖的大体流程,这里就不文字进行描述过程了,咱们能够直接将抽奖部分的全部状态抽出来,每一个状态做为一个单独的状态机状态,而后绘制出抽奖这个模块的状态转移过程:
Start
状态能够忽略,仅仅是一个描述的起点,在前端能够看作是拿到数据以前的页面状态。
抽奖模块的核心就是一个简单的按钮,经过上面的状态机能够看到,这个按钮的文案状态总共有5种,每一种对应的操做都是不同的。
经过这五种状态,咱们能够将一个按钮的功能拆分红3个部分:文案(text)、样式(style)以及点击事件(clickCallback)。
五种状态能够直接映射到3个实际的部分:
() => {}
若是没有这个状态机,你的代码会写成什么样子呢?
无数的if
,或者是看起来很工整,可是全是冗余的switch case
?
如今你能够愉快地Coding了,若是你用的是React,你会发现这整个逻辑能够完成抽出成为一个单独的HOC(高阶组件)。不管之后产品狗们给你加多少的业务逻辑或者状态,你的抽奖模块就永远只须要修改一个map
对象,或者一个switch case
,这个HOC彷佛就是完成与其余内容隔离的东西。
假设后端针对每一种状态给咱们的数据是一个统一的对象:
{
userId: 123,
count: 20, // 剩余抽奖次数
expireTime: 20901831313, // 抽奖过时时间
lottery: {
awardName: '拖鞋', // 奖品名称
},
isWinning: false, // 上次是否抽中
address: {
name: null,
address: null
}
}
复制代码
根据这个对象咱们就能够获得当前状态以及状态转移了。
咱们的HOC接收一个对象以及一个组件做为参数,固然对象就是上面后端给到的数据对象,而组件就是无状态的抽奖按钮组件了。
// action.js
// 在action中完成全部的状态转移
const lottery = (state) => {
return dispatch => {
return fetch('/api/lottery').then(res => {
// 根据抽奖状态进行状态转移
if (res.code === 200) {
dispatch(hasQulification(res));
} else {
dispatch(lotteryExpire());
}
})
}
}
// reducer.js
const lottery = (state = {
count: 0,
status: 'noQualification'
}) {
switch (action.type) {
case HAS_QUALIFICATION:
return {
status: 'hasQualification',
count: state.count
};
// ....其余状态
}
}
// lotteryHOC.js
export default (Component, lotteryData) => {
const statusMap = {
'hasQulification': {
text: '当即抽奖',
clazz: 'active',
cb: () => {
dispatch(lottery()); // 请求下次抽奖结果
}
},
'noQulification': {
// 下面的代码就省略了,这个对象就是用来Map状态到实际的样式和行为的
}
}
return class Wrapper extends React.PureComponent {
render() {
const {status} = this.props;
return (
<Component {...statusMap[status]} /> ) } } } 复制代码
这里简单写了一些逻辑代码,能够看到将状态和行为分离以后,业务组件里面的逻辑变的很是清晰,增长状态须要修改的地方也更加方便。若是你的业务架构中使用了Redux,它能够帮助你将全部的状态转移都抽到业务代码以外,保持业务代码和受控组件的纯净度。
其实我的认为,在不少时候,代码不须要很是精炼,由于多几十行代码并不会带来很大的性能损失,可是杂乱的代码确定会致使之后维护的时候很是高的回归成本。
和上篇文章不同的地方在于,这一篇更贴近实际工做中的业务场景。在第一次实现这种复杂逻辑场景的时候,我并无以为这是一件须要思考的事情,可是当策划修改了一个地方的需求的时候,个人老阔开始痛了。
因而在第二次接受到这种需求的时候,花了很长时间来理顺业务的逻辑,而后画图,实现,这样一步步下来,不管需求如何变动,均可以愉快地在排期的时候多申请两天,而后快速改完,撸两天本身的兴趣。
因此,状态机并非多么高不可攀的理论,在实际业务中能够很容易将其结合,而后提高本身的开发和迭代效率的。也可让本身少掉许多头发哦!!