大家在寫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的狀態管理,大家要知道原理,重在應用,等用多了你再去看源碼,就能夠更加深刻的體會作者為什麼那麼寫,原理和熟練應用一個都不能少,但是要注意先後順序和特定學習階段該了解到的程度。