<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          利用 XState(有限狀態(tài)機(jī)) 編寫(xiě)易于變更的代碼

          共 6926字,需瀏覽 14分鐘

           ·

          2020-10-24 09:20

          作者:jump jump

          來(lái)源:SegmentFault 思否




          目前來(lái)說(shuō),無(wú)論是 to c 業(yè)務(wù),還是 to b 業(yè)務(wù),對(duì)于前端開(kāi)發(fā)者的要求越來(lái)越高,各種絢麗的視覺(jué)效果,復(fù)雜的業(yè)務(wù)邏輯層出不窮。針對(duì)于業(yè)務(wù)邏輯而言,貫穿后端業(yè)務(wù)和前端交互都有一個(gè)關(guān)鍵點(diǎn) —— 狀態(tài)轉(zhuǎn)換。


          當(dāng)然了,這種代碼實(shí)現(xiàn)本身并不復(fù)雜,真正的難點(diǎn)在于如何快速的進(jìn)行代碼的修改。


          在實(shí)際開(kāi)發(fā)項(xiàng)目的過(guò)程中,ETC 原則,即 Easier To Change,易于變更是非常重要的。為什么解耦很好?為什么單一職責(zé)很有用?為什么好的命名很重要?因?yàn)檫@些設(shè)計(jì)原則讓你的代碼更容易發(fā)生變更。ETC 甚至可以說(shuō)是其他原則的基石,可以說(shuō),我們現(xiàn)在所作的一切都是為了更容易變更??!特別是針對(duì)于初創(chuàng)公司,更是如此。


          例如:項(xiàng)目初期,當(dāng)前的網(wǎng)頁(yè)有一個(gè)模態(tài)框,可以進(jìn)行編輯,模態(tài)框上有兩個(gè)按鈕,保存與取消。這里就涉及到模態(tài)框的顯隱狀態(tài)以及權(quán)限管理。隨著時(shí)間的推移,需求和業(yè)務(wù)發(fā)生了改變。當(dāng)前列表無(wú)法展示該項(xiàng)目的所有內(nèi)容,在模態(tài)框中我們不但需要編輯數(shù)據(jù),同時(shí)需要展示數(shù)據(jù)。這時(shí)候我們還需要管理按鈕之間的聯(lián)動(dòng)。僅僅這些就較為復(fù)雜,更不用說(shuō)涉及多個(gè)業(yè)務(wù)實(shí)體以及多角色之間的細(xì)微控制。


          重新審視自身代碼,雖然之前我們做了大量努力利用各種設(shè)計(jì)原則,但是想要快速而安全的修改散落到各個(gè)函數(shù)中的狀態(tài)修改,還是非常浪費(fèi)心神的,而且還很容易出現(xiàn)“漏網(wǎng)之魚(yú)”。


          這時(shí)候,我們不僅僅需要依靠自身經(jīng)驗(yàn)寫(xiě)好代碼,同時(shí)也需要一些工具的輔助。




          有限狀態(tài)機(jī)


          有限狀態(tài)機(jī)是一個(gè)非常有用的數(shù)學(xué)計(jì)算模型,它描述了在任何給定時(shí)間只能處于一種狀態(tài)的系統(tǒng)的行為。當(dāng)然,該系統(tǒng)中只能夠建立出一些有限的、定性的“模式”或“狀態(tài)” ,并不描述與該系統(tǒng)相關(guān)的所有(可能是無(wú)限的)數(shù)據(jù)。例如,水可以是四種狀態(tài)中的一種: 固體(冰)、液體、氣體或等離子體。然而,水的溫度可以變化,它的測(cè)量是定量的和無(wú)限的。


          總結(jié)來(lái)說(shuō),有限狀態(tài)機(jī)的三個(gè)特征為:


          • 狀態(tài)總數(shù)(state)是有限的。
          • 任一時(shí)刻,只處在一種狀態(tài)之中。
          • 某種條件下,會(huì)從一種狀態(tài)轉(zhuǎn)變(transition)到另一種狀態(tài)。

          在實(shí)際開(kāi)發(fā)中,它還需要:

          • 初始狀態(tài)
          • 觸發(fā)狀態(tài)變化的事件和轉(zhuǎn)換函數(shù)
          • 最終狀態(tài)的集合(有可能是沒(méi)有最終狀態(tài))

          先看一個(gè)簡(jiǎn)單的紅綠燈狀態(tài)轉(zhuǎn)換:

          const?light?=?{
          ??currentState:?'green',
          ??
          ??transition:?function?()?{
          ????switch?(this.currentState)?{
          ??????case?"green":
          ????????this.currentState?=?'yellow'
          ????????break;
          ??????case?"yellow":
          ????????this.currentState?=?'red'
          ????????break;
          ??????case?"red":?
          ????????this.currentState?=?'green'
          ????????break;
          ??????default:
          ????????break;
          ????}
          ??}
          }

          有限狀態(tài)機(jī)在游戲開(kāi)發(fā)中大放異彩,已經(jīng)成為了一種常用的設(shè)計(jì)模式。用這種方式可以使每一個(gè)狀態(tài)都是獨(dú)立的代碼塊,與其他不同的狀態(tài)分開(kāi)獨(dú)立運(yùn)行,這樣很容易檢測(cè)遺漏條件和移除非法狀態(tài),減少了耦合,提升了代碼的健壯性,這么做可以使得游戲的調(diào)試變得更加方便,同時(shí)也更易于增加新的功能。

          對(duì)于前端開(kāi)發(fā)來(lái)說(shuō),我們可以從其他工程領(lǐng)域中多年使用的經(jīng)驗(yàn)學(xué)習(xí)與再創(chuàng)造。



          XState 體驗(yàn)


          實(shí)際上開(kāi)發(fā)一個(gè) 簡(jiǎn)單的狀態(tài)機(jī)并不是特別復(fù)雜的事情,但是想要一個(gè)完善,實(shí)用性強(qiáng),還具有可視化工具的狀態(tài)機(jī)可不是一個(gè)簡(jiǎn)單的事。

          這里我要推薦?XState,該庫(kù)用于創(chuàng)建、解釋和執(zhí)行有限狀態(tài)機(jī)和狀態(tài)圖。

          簡(jiǎn)單來(lái)說(shuō):上述的代碼可以這樣寫(xiě)。

          import?{?Machine?}?from?'xstate'

          const?lightMachine?=?Machine({
          ??//?識(shí)別?id,?SCXML?id?必須唯一
          ??id:?'light',
          ??//?初始化狀態(tài),綠燈
          ??initial:?'green',
          ??
          ??//?狀態(tài)定義?
          ??states:?{
          ????green:?{
          ??????on:?{
          ????????//?事件名稱,如果觸發(fā)?TIMRE?事件,直接轉(zhuǎn)入?yellow?狀態(tài)
          ????????TIMRE:?'yellow'
          ??????}
          ????},
          ????yellow:?{
          ??????on:?{
          ????????TIMER:?'red'
          ??????}
          ????},
          ????red:?{
          ??????on:?{
          ????????TIMER:?'green'
          ??????}
          ????}
          ??}
          })

          //?設(shè)置當(dāng)前狀態(tài)
          const?currentState?=?'green'

          //?轉(zhuǎn)換的結(jié)果
          const?nextState?=?lightMachine.transition(currentState,?'TIMER').value?
          //?=>?'yellow'

          //?如果傳入的事件沒(méi)有定義,則不會(huì)發(fā)生轉(zhuǎn)換,如果是嚴(yán)格模式,將會(huì)拋出錯(cuò)誤
          lightMachine.transition(currentState,?'UNKNOWN').value?

          其中?SCXML?是狀態(tài)圖可擴(kuò)展標(biāo)記語(yǔ)言, XState 遵循該標(biāo)準(zhǔn),所以需要提供 id。當(dāng)前狀態(tài)機(jī)也可以轉(zhuǎn)換為 JSON 或 SCXML。

          雖然 transition 是一個(gè)純函數(shù),非常好用,但是在真實(shí)環(huán)境使用狀態(tài)機(jī),我們還是需要更強(qiáng)大的功能。如:

          • 跟蹤當(dāng)前狀態(tài)
          • 執(zhí)行副作用
          • 處理延遲過(guò)度以及時(shí)間
          • 與外部服務(wù)溝通

          XState 提供了 interpret 函數(shù),

          import?{?Machine,interpret?}?from?'xstate'

          //?。。。lightMachine 代碼

          //?狀態(tài)機(jī)的實(shí)例成為?serivce
          const?lightService?=?interpret(lightMachine)
          ???//?當(dāng)轉(zhuǎn)換時(shí)候,觸發(fā)的事件(包括初始狀態(tài))
          ??.onTransition(state?=>?{
          ????//?返回是否改變,如果狀態(tài)發(fā)生變化(或者?context?以及?action?后文提到),返回?true?
          ????console.log(state.changed)?
          ????console.log(state.value)
          ??})
          ??//?完成時(shí)候觸發(fā)
          ??.onDone(()?=>?{
          ????console.log('done')
          ??})

          //?開(kāi)啟
          lightService.start()

          //?將觸發(fā)事件改為?發(fā)送消息,更適合狀態(tài)機(jī)風(fēng)格
          //?初始化狀態(tài)為?green?綠色
          lightService.send('TIMER')?//?yellow
          lightService.send('TIMER')?//?red

          //?批量活動(dòng)
          lightService.send([
          ??'TIMER',
          ??'TIMER'
          ])

          //?停止
          lightService.stop()

          //?從特定狀態(tài)啟動(dòng)當(dāng)前服務(wù),這對(duì)于狀態(tài)的保存以及使用更有作用
          lightService.start(previousState)

          我們也可以結(jié)合其他庫(kù)在 Vue React 框架中使用,僅僅只用幾行代碼就實(shí)現(xiàn)了我們想要的功能。

          import?lightMachine?from?'..'
          //?react?hook?風(fēng)格
          import?{?useMachine?}?from?'@xstate/react'

          function?Light()?{
          ??const?[light,?send]?=?useMachine(lightMachine)
          ??
          ??return?<>
          ????//?當(dāng)前狀態(tài)?state?是否是綠色
          ????{light.matches('green')?&&?'綠色'}????
          ????//?當(dāng)前狀態(tài)的值
          ????{light.value}??
          ????//?發(fā)送消息
          ?????send('TIMER')}>切換
          ??
          }

          當(dāng)前的狀態(tài)機(jī)也是還可以進(jìn)行嵌套處理,在紅燈狀態(tài)下添加人的行動(dòng)狀態(tài)。

          import?{?Machine?}?from?'xstate';

          const?pedestrianStates?=?{
          ??//?初識(shí)狀態(tài)?行走
          ??initial:?'walk',
          ??states:?{
          ????walk:?{
          ??????on:?{
          ????????PED_TIMER:?'wait'
          ??????}
          ????},
          ????wait:?{
          ??????on:?{
          ????????PED_TIMER:?'stop'
          ??????}
          ????},
          ????stop:?{}
          ??}
          };

          const?lightMachine?=?Machine({
          ??id:?'light',
          ??initial:?'green',
          ??states:?{
          ????green:?{
          ??????on:?{
          ????????TIMER:?'yellow'
          ??????}
          ????},
          ????yellow:?{
          ??????on:?{
          ????????TIMER:?'red'
          ??????}
          ????},
          ????red:?{
          ??????on:?{
          ????????TIMER:?'green'
          ??????},
          ??????...pedestrianStates
          ????}
          ??}
          });

          const?currentState?=?'yellow';

          const?nextState?=?lightMachine.transition(currentState,?'TIMER').value;

          //?返回級(jí)聯(lián)對(duì)象?
          //?=>?{
          //???red:?'walk'
          //?}

          //?也可以寫(xiě)為?red.walk
          lightMachine.transition('red.walk',?'PED_TIMER').value;

          //?轉(zhuǎn)化后返回
          //?=>?{
          //???red:?'wait'
          //?}

          //?TIMER?還可以返回下一個(gè)狀態(tài)
          lightMachine.transition({?red:?'stop'?},?'TIMER').value;
          //?=>?'green'

          當(dāng)然了,既然有嵌套狀態(tài),我們還可以利用 type: 'parallel' ,進(jìn)行串行和并行處理。

          除此之外,XState 還有擴(kuò)展?fàn)顟B(tài) context 和過(guò)度防護(hù) guards。這樣的話,更能夠模擬現(xiàn)實(shí)生活

          //?是否可以編輯
          functions?canEdit(context:?any,?event:?any,?{?cond?}:?any)?{
          ??console.log(cond)
          ??//?=>?delay:?1000
          ??
          ??//?是否有某種權(quán)限????
          ??return?hasXXXAuthority(context.user)
          }


          const?buttonMachine?=?Machine({
          ??id:?'buttons',
          ??initial:?'green',
          ??//?擴(kuò)展?fàn)顟B(tài),例如?用戶等其他全局?jǐn)?shù)據(jù)
          ??context:?{
          ????//?用戶數(shù)據(jù)
          ????user:?{}
          ??},
          ??states:?{
          ????view:?{
          ??????on:?{
          ????????//?對(duì)應(yīng)之前?TIMRE:?'yellow'
          ????????//?實(shí)際上?字符串無(wú)法表達(dá)太多信息,需要對(duì)象表示
          ????????EDIT:?{
          ??????????target:?'edit',
          ??????????//?如果沒(méi)有該權(quán)限,不進(jìn)行轉(zhuǎn)換,處于原狀態(tài)
          ??????????//?如果沒(méi)有附加條件,直接?cond:?searchValid
          ??????????cond:?{
          ????????????type:?'searchValid',
          ????????????delay:?3
          ??????????}
          ????????},?
          ??????}
          ????}
          ??}
          },?{
          ??//?守衛(wèi)
          ??guards:?{
          ????canEdit,
          ??}
          })


          //?XState?給予了更加合適的?API?接口,開(kāi)發(fā)時(shí)候?Context?可能不存在
          //?或者我們需要在不同的上下文?context?中復(fù)用狀態(tài)機(jī),這樣代碼擴(kuò)展性更強(qiáng)
          const?buttonMachineWithDelay?=?buttonMachine.withContext({
          ??user:?{},
          ??delay:?1000
          })

          //?withContext?是直接替換,不進(jìn)行淺層合并,但是我們可以手動(dòng)合并
          const?buttonMachineWithDelay?=?buttonMachine.withContext({
          ??...buttonMachine.context,
          ??delay:?1000
          })

          我們還可以通過(guò)瞬時(shí)狀態(tài)來(lái)過(guò)度,瞬態(tài)狀態(tài)節(jié)點(diǎn)可以根據(jù)條件來(lái)確定機(jī)器應(yīng)從先前的狀態(tài)真正進(jìn)入哪個(gè)狀態(tài)。瞬態(tài)狀態(tài)表現(xiàn)為空字符串,即 '',如

          const?timeOfDayMachine?=?Machine({
          ??id:?'timeOfDay',
          ??//?當(dāng)前不知道是什么狀態(tài)
          ??initial:?'unknown',
          ??context:?{
          ????time:?undefined
          ??},
          ??states:?{
          ????//?Transient?state
          ????unknown:?{
          ??????on:?{
          ????????'':?[
          ??????????{?target:?'morning',?cond:?'isBeforeNoon'?},
          ??????????{?target:?'afternoon',?cond:?'isBeforeSix'?},
          ??????????{?target:?'evening'?}
          ????????]
          ??????}
          ????},
          ????morning:?{},
          ????afternoon:?{},
          ????evening:?{}
          ??}
          },?{
          ??guards:?{
          ????isBeforeNoon:?//...?確認(rèn)當(dāng)前時(shí)間是否小于?中午?
          ????isBeforeSix:?//?...?確認(rèn)當(dāng)前時(shí)間是否小于?下午?6?點(diǎn)
          ??}
          });

          const?timeOfDayService?=?interpret(timeOfDayMachine
          ??.withContext({?time:?Date.now()?}))
          ??.onTransition(state?=>?console.log(state.value))
          ??.start();

          timeOfDayService.state.value?
          //?根據(jù)當(dāng)前時(shí)間,可以是?morning?afternoon?和?evening,而不是?unknown?轉(zhuǎn)態(tài)


          到這里,我覺(jué)得已經(jīng)介紹 XState 很多功能了,篇幅所限,不能完全介紹所有功能,不過(guò)當(dāng)前的功能已經(jīng)足夠大部分業(yè)務(wù)需求使用了。如果有其他更復(fù)雜的需求,可以參考?XState 文檔。

          這里列舉一些沒(méi)有介紹到的功能點(diǎn):

          • 進(jìn)入和離開(kāi)某狀態(tài)觸發(fā)動(dòng)作(action 一次性)和活動(dòng)(activity 持續(xù)性觸發(fā),直到離開(kāi)某狀態(tài))
          • 延遲事件與過(guò)度 after
          • 服務(wù)調(diào)用 invoke,包括 promise 以及 兩個(gè)狀態(tài)機(jī)之間相互交互
          • 歷史狀態(tài)節(jié)點(diǎn),可以通過(guò)配置保存狀態(tài)并且回退狀態(tài)

          當(dāng)然了,對(duì)比于 x-state 這種,還有其他的狀態(tài)機(jī)工具,如?javascript-state-machine?,?Ego?等。大家可以酌情考慮使用。



          總結(jié)


          對(duì)于現(xiàn)代框架而言,無(wú)論是如火如荼的 React Hook 還是漸入佳境的 Vue Compoistion Api,其本質(zhì)都想提升狀態(tài)邏輯的復(fù)用能力。但是考慮大部分場(chǎng)景下,狀態(tài)本身的切換都是有特定約束的,如果僅僅靠良好的編程習(xí)慣,恐怕還是難以寫(xiě)出抑郁修改的代碼。而 FSM 以及 XState 無(wú)疑是一把利器。



          鼓勵(lì)一下


          如果你覺(jué)得這篇文章不錯(cuò),希望可以給與我一些鼓勵(lì),在我的 github 博客下幫忙 star 一下。

          博客地址?: https://github.com/wsafight/personBlog




          參考


          • XState 文檔?: https://xstate.js.org/docs/

          • JavaScript與有限狀態(tài)機(jī)?:?http://www.ruanyifeng.com/blog/2013/09/finite-state_machine_for_javascript.html






          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開(kāi)更多互動(dòng)和交流。



          -?END -

          瀏覽 115
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久伊人在 | 日韩伦理色片一区二区 | 91二区三区网站 | 操逼逼片 | 人人草视频在线观看 |