大家在写App和一些单页面程序的时候,经常会遇到这样的情况:
当点击左边的箭头的时候,会出现灰黑色的抽屉效果,再点击一下向左的箭头,就会收起来,当然向右滑动和向左滑动也能实现抽屉效果的开关。还有,当抽屉效果开著的时候,点击右侧区域也会自动收起抽屉。点击左侧抽屉里面的图标,那么也会发生抽屉效果的开关。如果我们用原生js去手动控制抽屉的开关效果,随著逻辑的复杂,会存在两个严重的问题:
1.如果只是向左箭头点击一次开,再点击一次关成对儿出现还好,但是如果出现比如点击一下左侧按钮或者点击右侧内容区域关闭就容易出现点击关闭不掉或者不该关闭的情况下关闭了,抽屉状态的控制会随著业务逻辑的复杂度增加变得十分不可控。
2.如果要想增加一个逻辑控制抽屉开关,那么就得从一堆的if else 详细代码里,找到最后的else 部分增加else if,一来是修改起来非常困难,而来如果遇上条件组合判断和嵌套,对于开发者简直是一个噩梦,如果出现一个bug,你得把所有的逻辑捋顺一遍,这对于一个复杂的项目是非常耗时,几乎是不可能的。
由于app越来越复杂,视图随著数据和逻辑的手动更新状态变得越来越困难。在这种情况下,Angualr、React、Vue等框架应运而生。尤其是React,在状态管理无出其右。那么问题来了,为什么React就在状态管理方面游刃有余呢?
因为它使用了状态模式。这事儿其实非常的简单,我们看看怎么回事儿。
什么是状态模式或状态机?
一个对象在其内部状态改变时改变它的行为,这个对象看起来就像改变了它的类一样。
当大家看到定义的时候,是不是有一种单个字都认识合起来完全看不懂的感觉?这就好比直播吃翔四个字,单看这四个字感觉没啥,合起来就感觉怎么这么恶心。
不过没关系,我们来看看状态模式到底是个啥玩意。大家千万别听见设计模式和状态机之类的词语自己先蒙圈吓到了,我直接写一个状态机,你一看就明白。
大家家里都用过冰箱吧,点击电源按钮.如果是洗衣机是开著的on状态, 当你点击它会发出off的信号,如果你是off状态,则会发出on的信号.代码实现如下:
var switches = (function(){ var state = "off"; return function(){ if(state === "off"){ console.log("打开洗衣机"); state = "on"; }else if(state === "on"){ console.log("关闭洗衣机"); state = "off"; } } })(); //按下开关按钮 oBtn.onclick = function(){ switches(); };
上面的代码完全符合我们学过的js的东西,简单的一个匿名函数自执行,加一个变数,ok这就是一个状态机,是不是so easy。
但是作为一个有追求的洗衣机,得能够针对不同的洗衣机选择的不同的程序,没错洗衣机面板上也是程序这两个字,发明洗衣机的一定是程序员。
程序里面有很多的洗涤方式,比如智能、标准、快速、强力,轻柔等等。我们丰富一下我们的面板。
var switches = (function(){ var state = "智能"; return function(){ if(state === "智能"){ console.log("智能洗衣模式"); state = "标准"; }else if(state === "标准"){ console.log("标准洗衣模式"); state = "快速"; }else if(state === "快速"){ console.log("快速洗衣模式"); state = "强力"; }else if(state === "强力"){ console.log("强力洗衣模式"); state = "轻柔"; }else if(state === "轻柔"){ console.log("轻柔洗衣模式"); state = "智能"; } } })(); //按下程序按钮 oBtn.onclick = function(){ switches(); };
看起来没问题,但是你懂的,首先写10中洗衣模式if写法会被写死,switch方式也很不好。
我有一个大胆的想法,能不能不用判断语句?大家看我这么思考能不能搞定这件事?
其实洗衣机面板其实就是一个状态对应一个行为:
这句话就好理解了,大家想洗衣机如果本来是浸泡状态,但是我把它改成脱水状态,洗衣机从最开始的浸泡衣服变成甩干衣服,看起来就好像洗衣机从一个浸泡机器变成了甩干机器一样。
换成这种思路理解,我们就可以不用判断了,只要记住状态改变,行为改变就行了。我们试著实现一下洗衣模式。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width_=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script> window.onload = function(){ //定义状态 var Zhineng = function(button){ this.turn = button; } Zhineng.prototype.press= function(){ console.log(智能模式); this.turn.setState("Biaozhun"); } var Biaozhun = function(button){ this.turn = button; } Biaozhun.prototype.press= function(){ console.log(标准模式); this.turn.setState("Kuaisu"); } var Kuaisu = function(button){ this.turn = button; } Kuaisu.prototype.press= function(){ console.log(快速模式); this.turn.setState("Qiangli"); } var Qiangli = function(button){ this.turn = button; } Qiangli.prototype.press= function(){ console.log(强力模式); this.turn.setState("Qingrou"); } var Qingrou = function(button){ this.turn = button; } Qingrou.prototype.press= function(){ console.log(轻柔模式); this.turn.setState("Zhineng"); } //定义洗衣机洗衣程序 var Washer = function(){ this.Zhineng = new Zhineng(this); this.Biaozhun = new Biaozhun(this); this.Kuaisu = new Kuaisu(this); this.Qiangli = new Qiangli(this); this.Qingrou = new Qingrou(this); this.state = "Zhineng"; } Washer.prototype.setState = function(state){ this.state=state; } Washer.prototype.press = function(){ this[this.state].press(); //执行对应状态的press } Washer.prototype.init = function(){ //定义执行者 document.querySelector(#btn).addEventListener("click",()=>{ this.press(); },false); } var w = new Washer(); w.init(); //初始化 w.state = "Qiangli"; }; </script> </head> <body> <input type="button" value="按下" id="btn"> </body> </html>
这样就拜托了if判断,我们想要什么模式,只需要通过改变对应的状态就好了。
但是很多人会想说:
大量重复代码太难受了啊,针对这样的情况,es6中推出了状态机这个伪函数,能够帮助我们快速实现状态化。
也就是传说中的generator函数。 目前FF,edge,chrome 最新版本已经支持。
我们用generator优化一下我们上面的代码:
<!DOCTYPE html> <html lang="en"> ? <head> <meta charset="UTF-8"> <meta name="viewport" content="width_=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script> window.onload = function () { //这里一堆变数,其实我们可以用一个对象,但是我们不搞那么复杂 var Zhineng = function () { console.log("智能模式"); } var Biaozhun = function () { console.log("标准模式"); } var Kuaisu = function () { console.log("快速模式"); } var Qiangli = function () { console.log("强力模式"); } var Qingrou = function () { console.log("轻柔模式"); } function* models() { for (var i = 0, fn, len = arguments.length; fn = arguments[i++];) { yield fn(); if (i === len) { i = 0; } } } var exe = models(Zhineng,Biaozhun,Kuaisu,Qiangli, Qingrou); //按照模式顺序排放 document.querySelector("#btn").addEventListener("click", function () { exe.next(); }, false); }; </script> </head> ? <body> <input type="button" value="按下" id="btn"> </body> ? </html>
1.function关键字与函数名之间有一个星号;
2.函数体内部使用yield表达式,定义不同的内部状态
3.遍历器对象的next方法,使得指针移向下一个状态
简单的说generator就是让函数排队一个一个去执行了,很简单对不对。
上面我们说过react的状态管理无出其右,那我们看看它的状态管理跟我们写的有什么异同之处呢?
我们平时开发组件时很多时候要切换组件的状态,每种状态有不同的处理方式,这个时候就可以使用状态模式进行开发。
用和上面一样的思路,我们来举一个React组件的小例子,比如一个Banner导航,最基本的两种状态,显示和隐藏,如下:
我们先把generator思路翻篇,我们回到状态模式,先看一段代码
const States = { "show": function () { console.log("banner展现状态,点击关闭"); this.setState({ currentState: "hide" }) }, "hide": function () { console.log("banner关闭状态,点击展现"); this.setState({ currentState: "show" }) } };
大家现在来看,这个不就是「react版本的洗衣机」嘛。只不过我们定义的是单个的状态变数,而这里通过一个对象States来定义banner的状态,这里有两种状态show和hide,分别拥有相应的处理方法,调用后再分别把当前banner改写为另外一种状态。
接下来,来看导航类Banner,这里吧状态要给拿过来做对照了,这样看著醒目:
const States = { "show": function () { console.log("banner展现状态,点击关闭"); this.setState({ currentState: "hide" }) }, "hide": function () { console.log("banner关闭状态,点击展现"); this.setState({ currentState: "show" }) } }; //上面代码不该出现在这只是为了说明问题 class Banner extends Component { constructor(props) { super(props); this.state = { currentState: "hide" } this.toggle = this.toggle.bind(this); } toggle() { const s = this.state.currentState; States[s] && States[s].apply(this); } ? render() { const { currentState } = this.state; return ( <div className="banner" onClick={this.toggle}> </div> ); } }; export default Banner;
通过导航组件大家这么想,其实react就是有很多种洗衣状态但是一定能数出来多少种洗衣模式的洗衣机,我们每一次的操作都是在按洗衣机的按钮。
上面的代码只不过是再开关洗衣机而已。
你不用纠结为什么这么做就能够进行状态管理。你用洗衣机强力模式的时候,你也没考虑过洗衣机桶和马达怎么运作的吧?你只要知道按下哪个按钮能洗干净衣服,大体上知道洗衣机里面是通过马达带动衣服和滚筒之间摩擦清除污渍就够了。
同理,你只要知道改变状态能够实现要的功能,大体上的原理就是状态机就可以了。
有了这个基础,如果想继续深入就可以去看react源码以及进行各种开发了。
1.状态机模式的使用场景,复杂多状态的管理,这里注意你没必要写个选项卡之类的用状态机,那反而是给自己找麻烦。技术源于生活和解决问题,你洗一双袜子也不会扔洗衣机里面吧?
2.generaor的使用,这个大家要学会和灵活运用,我只是浅显的讲了基础的应用,这个对你的开发和代码简化会大有用途。
3.react的状态管理,大家要知道原理,重在应用,等用多了你再去看源码,就能够更加深刻的体会作者为什么那么写,原理和熟练应用一个都不能少,但是要注意先后顺序和特定学习阶段该了解到的程度。