結合狀態機的開發風格

本文主要以XXX的html5版本爲藍本,討論結合狀態機開發的思路和實踐方式。狀態機選型使用statechart.js

起步知識

特別是狀態機介紹,內容非常好,強烈推薦。

適用場景

  • 主要用於某個具體業務的複雜頁面流控制
  • 簡單的業務流程是不需要的。例如只有一兩個頁面(列表+詳情)
  • 適用於多步驟多頁面(包括彈出框)、各種跳轉的場景

如何定義狀態?

根據頁面流、步驟來定義狀態。可以參考以下步驟:

  • 對照保真、流程圖,劃分每個獨立頁面
    以個人營銷活動爲例,主要頁面包括活動頁面、選檔次頁面、獎品頁面、獎品包選擇頁面、繳費頁面、發票頁面。
    那麼可以考慮定義爲list、level、reward、giftpack、charge、invoice

  • 對於有多種彈出窗口的情況,可以考慮定義子狀態
    以推薦業務爲例,在菜單頁面上,可能會彈出反饋窗口,或者產品訂購窗口。
    那麼可以考慮定義爲menu/index、menu/feedback、menu/prod,這樣的話,通過下面的頁面控制,就可以讓在值狀態的情況下,菜單頁面一直顯示。

<div ng-show="fsm.isCurrent('/menu')" ng-include="'app/partials/recommended/recommended_menu.html'"></div>
<div ng-show="fsm.isCurrent('/menu/prod')" ng-include="'app/partials/recommended/recommended_orderprod.html'"></div>
<div ng-show="fsm.isCurrent('/menu/feedback')" ng-include="'app/partials/recommended/recommended_feedback.html'"></div>
  • 對於頁面顯示,有較多共性的頁面,可以考慮定義子狀態,方便共享邏輯和事件處理
    以上述的個人營銷活動爲例,獎品頁面、獎品包選擇頁面的頁面很類似,功能操作也比較實現,可以定義成子狀態,如order/reward、order/giftpack
<div ng-show="fsm.isCurrent('/list')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_list.html'"></div>
<div ng-show="fsm.isCurrent('/level')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_level.html'"></div>
<div ng-show="fsm.isCurrent('/order/reward')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_reward.html'"></div>
<div ng-show="fsm.isCurrent('/order/giftpack')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_giftpack.html'"></div>
<div ng-show="fsm.isCurrent('/invoice')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_invoice.html'"></div>
<div ng-show="fsm.isCurrent('/charge')" ng-include="'app/partials/personalMarketCamp/personalMarketCamp_charge.html'"></div>

如何控制頁面的顯示、如何響應頁面操作?

  • 頁面顯示與否,通過狀態機的狀態,而不是數據的狀態。這裏用的是isCurrent方法
  • 頁面操作,通過狀態機的事件發送,而不是直接使用綁定在$scope的方法。這裏用的是send方法
  • 頁面跳轉,通過狀態機的狀態變化來驅動。這裏用的是goto方法,是在send方法之後的event邏輯中處理的。

頁面顯示與否,例子上面已經說了。而對於ng-click這種事件觸發,直接用send方法即可。

<div class="Feedback-btn">
  <a ng-click="fsm.send('feedback', 'hesitate')" class="accept-btn"><span>考慮</span></a>
  <a ng-click="fsm.send('feedback', 'refuse')" class="refuse-btn"><span>拒絕</span></a>
</div>

可以看到,事件也可以捎帶參數的,這樣可以在該狀態的event中進行處理,如下:

# 反饋
@state 'feedback', ->
  # 進行反饋操作
  @event 'feedback', (operationtype) ->
    product = $scope.viewModel.product
    if product.opertype == '1'
      new Toast(
        context: $('body')
        message: "該產品不可推薦"
      ).show();
      return

    qryfeedbackService.event
      "userseq": $scope.viewModel.product.userseq
      "servnumber": $scope.telnum
      "operationtype": operationtype
    .then (ok) =>
        @goto '/menu/index'
      , (err) ->
        new Toast(
          context: $('body')
          message: err
        ).show()

需要注意的是,event是掛靠在某個狀態下的,如果你是子狀態的話的,event會先在子狀態中找,如果沒有找到會在父狀態上找。
通過這種方式,就可以實現多個子狀態共享event,例如獎品頁面、獎品包頁面都有選擇功能,就可以把這個操作放到父狀態的event中去。

更多狀態機的細節

很多狀態機都實現了某些特殊狀態,如進入狀態,退出狀態這種事件。statechart也實現了,對應的是enter和exit,代碼大體上是:

@state 'menu', ->
  @enter ->
    #TODO

  @exit ->
    #TODO

但是需要注意的是,重複進入這個狀態的話,是會重複執行的。所以對於A -> B -> C這樣的業務流程,從A到B和C回退到B,都會執行這個enter,
就無法區分這種情況了。因爲通常,從A到B是進行初始化,而從C回到B得保留原來B的數據狀態。所以實際上我很少使用這些特殊事件,除非:

  • 無需區分的情況,這樣寫會讓代碼風格更統一。
  • 沒有接口交互,本地操作的話,因爲這種消耗小很多。
  • 存在直接跳轉到該狀態的情況(例如由另外的業務跳轉過來),這種特殊情況下前面的步驟都被忽略。而且這種情況下,需要通常需要接口交互(例如補充某些必要信息),而爲了區分回退的情況,我通常會根據業務特性考慮一些數據緩存處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章