從React Redux的實際業務場景來看有限狀態機

寫在前面

上一篇:從Promise的實現來看有限狀態機javascript

上一篇講到了一個簡單的,利用了有限狀態機的前端實現PromisePromise的有限狀態機除了start以及finish兩個狀態,其核心的三個狀態其實就是一個異步行爲的三種狀態:PENDINGFULFILLEDREJECTED。經過異步行爲的狀態轉移,Promise提供了一種將嵌套回調函數的調用方式儘可能地扁平化,造成了一個鏈式的異步操做到同步操做的狀態轉換前端

除了Promise,前端還有不少的方案是基於有限狀態機數學模型來進行實現的。此次來結合實際業務,稍微聊聊目前前端數據狀態管理的最熱門的方案Redux吧。java

React & Redux

ReduxFlux

說到Redux,就不得不提及Flux。Flux說到底仍是一種數據管理的理論,而且有不少種的實現。通常見到的都是facebook推廣的實現方案。Flux理論的核心就是單向數據流,也就是全部的前端數據都是單向流動的。後端

前端的數據改變,通常都是由一些用戶操做觸發的。當某個用戶操做,觸發了某種狀態的改變。像蝴蝶效應同樣,又引發了其餘組件或者模塊的狀態改變;或者是觸發了某些數據請求,在異步的請求完成以後,新的數據被返回到前端,前端根據這些新的數據,進行頁面狀態的改變。api

Flux將這整個過程拆分爲幾個部分:前端框架

  1. Action:狀態改變的source,觸發Dispatcher;
  2. Dispatcher:接收Action,負責實際的狀態改變行爲;
  3. Store:數據或者狀態的存儲器,而且在狀態改變的時候,將內容分發到每一個組件;
  4. View:實際觸發Action的模塊,是和用戶進行交互的部分。

這四個因素造成一個數據或者說是狀態流動的閉環,而且狀態在這個閉環中流動都是單向的。架構

Redux和Flux的數據流過程基本一致,二者最大的不一樣在於Redux依賴Reducer來進行實際上的狀態修改,經過一個pure function來返回一個新的狀態,而且和舊的狀態進行合併,來觸發View的從新渲染。app

這個狀態轉移過程是否是和上篇文章中說的有限狀態機的思想有點相似呢?是的,這裏的數據就是狀態機中的狀態框架

ReactRedux

做爲目前最爲熱門的MVC前端框架,React自己具備多少優雅的特性就再也不贅言了。繁瑣的狀態管理可能讓不少人在進行React開發的時候,須要很長時間來進行組件的拆分以及重構。異步

在業務環境中,沒有人可以在一開始就設計一個完美的組件樹結構,尤爲是對於較大型的業務頁面。隨着需求的修改(。。。),隨着後端的接口的變化(。。。。。。。),你開始設計出來的組件結構以及狀態老是出現漏洞,而後修修補補提測了,上線了。當你進行二次開發,或者新的迭代的時候,你就會發現本來的組件結構讓你想死的心都有了,而後不停地重構,再重構。

React全部組件的變化都依賴state以及props兩個對象,一個來自於外部,一個是自驅動發生改變的。

其實React的stateprops也是有限狀態機中的狀態,而每一個具備狀態的React組件,就是一個獨立的狀態機。

Redux爲React提供了一種將分佈在各個組件中的state進行統一管理的思想或者說是工具。

可是Redux存在不少問題,其中最爲顯著的就是若是將整個頁面全部的可變狀態收集到一個store中保存,這個store可能會變得很龐大,某些無用的狀態致使store中存在不少冗餘。

經過有限狀態機組織React Redux的複雜業務邏輯

先描述一個業務場景:咱們須要一個很長的活動頁面,這個頁面中有展現、評論、點贊、抽獎、試聽等多個模塊,首屏數據經過batch從服務端獲取,以後的每次數據請求都是經過單獨的接口進行的。

業務分解

上面的這些模塊,有哪些須要經過狀態機的模型來進行狀態管理呢?

通常來講,無狀態的展現組件是不須要進行狀態管理的,因此展現、試聽這種模塊是不須要的。

其次,點贊是一個布爾的變化,只有成功和失敗兩種狀態,也不須要咱們如此費心去管理狀態。

那麼評論可能爭議很大,看似比較複雜的模塊其實並無太多的邏輯,可能評論區域顯示的內容不少,但大多都是靜態內容,也沒有太多的狀態改變,而且評論部分數據量比較大,若是採用Redux會致使store的結構不容易扁平化,形成組件性能損失。

剩下的就是抽獎了,抽獎的狀態很是多,比較難以管理,而且數據內容較少,很適合進行集中式地狀態管理,隨着業務的迭代,抽獎可能會發展出多種抽獎條件,這樣修改源代碼的難度也會較大。整個業務邏輯可能就變成了下面這樣。

繪製抽獎狀態機

爲了讓你們可以理解抽獎的大體流程,這裏就不文字進行描述過程了,咱們能夠直接將抽獎部分的全部狀態抽出來,每一個狀態做爲一個單獨的狀態機狀態,而後繪製出抽獎這個模塊的狀態轉移過程:

Start狀態能夠忽略,僅僅是一個描述的起點,在前端能夠看作是拿到數據以前的頁面狀態。

抽獎模塊的核心就是一個簡單的按鈕,經過上面的狀態機能夠看到,這個按鈕的文案狀態總共有5種,每一種對應的操做都是不同的。

經過這五種狀態,咱們能夠將一個按鈕的功能拆分紅3個部分:文案(text)、樣式(style)以及點擊事件(clickCallback)

五種狀態能夠直接映射到3個實際的部分:

當即抽獎

  1. Text:當即抽獎
  2. Style:active
  3. clickCallback:完成抽獎操做,向後端進行數據請求,而且根據以後的結果進行狀態轉移,抽中轉移到狀態填寫收貨地址中。未抽中則能夠根據剩餘次數轉移狀態到當即抽獎或者開通XX抽獎中。

開通XX抽獎

  1. Text:開通XX當即抽獎
  2. Style:active
  3. clickCallback:跳轉到開通XX的頁面,引導用戶開通XX來進行抽獎,開通則狀態轉移至當即抽獎,不然保留當前狀態

抽獎活動過時

  1. Text:抽獎活動過時
  2. Style:disable
  3. clickCallback:() => {}

填寫收貨地址

  1. Text:填寫收貨地址
  2. Style:active
  3. clickCallback:彈出填寫收貨地址的對話框,而且在用戶填寫完成以後,狀態轉移到確認收貨地址中,若是用戶長時間未填寫,則狀態轉移到抽獎活動過時中。

確認收貨地址

  1. Text:確認收貨地址
  2. Style:active
  3. clickCallback:彈出確認收貨地址的對話框,而且在用戶二次確認完成以後,狀態轉移到Finish中,若是用戶長時間未確認,則狀態轉移到抽獎活動過時中。

愉快地Coding

若是沒有這個狀態機,你的代碼會寫成什麼樣子呢?

無數的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,它能夠幫助你將全部的狀態轉移都抽到業務代碼以外,保持業務代碼和受控組件的純淨度。

其實我的認爲,在不少時候,代碼不須要很是精煉,由於多幾十行代碼並不會帶來很大的性能損失,可是雜亂的代碼確定會致使之後維護的時候很是高的迴歸成本。

總結一哈

和上篇文章不同的地方在於,這一篇更貼近實際工做中的業務場景。在第一次實現這種複雜邏輯場景的時候,我並無以爲這是一件須要思考的事情,可是當策劃修改了一個地方的需求的時候,個人老闊開始痛了。

因而在第二次接受到這種需求的時候,花了很長時間來理順業務的邏輯,而後畫圖,實現,這樣一步步下來,不管需求如何變動,均可以愉快地在排期的時候多申請兩天,而後快速改完,擼兩天本身的興趣。

因此,狀態機並非多麼高不可攀的理論,在實際業務中能夠很容易將其結合,而後提高本身的開發和迭代效率的。也可讓本身少掉許多頭髮哦!!

相關文章
相關標籤/搜索