成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

使用有限狀態(tài)機管理狀態(tài)

hiyang / 2009人閱讀

摘要:集成到去使用如果想在中使用,想到比較方便的使用形式是高階組件,需要用到有限狀態(tài)機的組件傳進高階組件,就立馬擁有了使用有限狀態(tài)機的能力。

背景

近年來由于一些前端框架的興起而后逐漸成熟,組件化的概念已經(jīng)深入人心,為了管理好大型應用中錯綜復雜的組件,又有了單向數(shù)據(jù)流的思想指引著我們,Vuex、Redux、MobX等狀態(tài)管理工具也許大家都信手拈來。
我們手握著這些工具,不斷思考著哪些數(shù)據(jù)應該放在全局,哪些數(shù)據(jù)應該局部消化,這樣那樣的數(shù)據(jù)該怎樣流轉(zhuǎn)。仔細想想會發(fā)現(xiàn),我們一直在做的是如何將數(shù)據(jù)存在合理的地方,進而去規(guī)范怎樣使用這些數(shù)據(jù),我們稱之為狀態(tài)管理,但我覺得好像只是做了狀態(tài)存儲與使用,卻沒有做好管理二字。嗯,總感覺差了些什么。

你能將狀態(tài)描述清楚嗎?

來看一段簡單的代碼:

state = {
    data: [
        {
            id: 1,
            userName: xxx
        },
        {
            id: 2,
            userName: yyy
        }
    ]
}

如果根據(jù)UI = f(state)來說,上面的state.data就是一個狀態(tài),它能直接反映視圖的樣子:

render () {
    const {data} = this.state
    return (
        
{ data && data.length ? data.map(item =>
{item.userName}
) : "暫無數(shù)據(jù)" }
) }

我們還會在合適的時機進行某種操作去更新狀態(tài),比如請求獲取數(shù)據(jù)的接口就會去更新上面的data:

updateData () {
  getData().then(({data}) => {
    this.setState({data})
  })
}

但隨著時間的推移,這樣的狀態(tài)會越來越多,更新狀態(tài)的方法暗藏在日益膨脹的代碼中,維護者可能自己都要如履薄冰地一翻抽絲剝繭才勉強捋清楚狀態(tài)什么時機更新,為什么要更新,更別說如果是去接盤一份祖?zhèn)鞔a了。
究其原因,我覺得是沒有將狀態(tài)描述清楚,更別說管理好狀態(tài)。所以一個描述得清楚的狀態(tài)是長什么樣子呢?比如:開始的時候,data是空數(shù)組,初始化完成需要去更新data,增刪改完成后都需要更新data。
想想日常,有沒有地方能一眼就看清楚這些狀態(tài)信息?需求文檔?UI稿?靠譜嗎?

有限狀態(tài)機了解一下

自己動手豐衣足食,我們的目標是在代碼里就能清晰地看到這些狀態(tài)信息。如果我們能夠?qū)懸环菖渲梦募韺⑺鼈兠枋銮宄?,然后寫代碼的時候就根據(jù)這份配置文件來寫,有修改的時候也必須先修改這份配置文件,那我們最后看配置文件就能對狀態(tài)信息一目了然了。
為了達到這樣的目標,我們得請有限狀態(tài)機來幫忙。概念性的東西請移步到JavaScript與有限狀態(tài)機,總的來說,有限狀態(tài)機是一個模型,它能描述清楚有哪些狀態(tài),狀態(tài)之間是怎樣轉(zhuǎn)化的,它有以下特點:
1.狀態(tài)的數(shù)量是固定的
2.狀態(tài)會因為觸發(fā)了某種行為而轉(zhuǎn)變成另一種狀態(tài)(比如典型的promise,初始狀態(tài)為pending,resolve后狀態(tài)轉(zhuǎn)變成fulfilled,reject則變成rejected)
3.任意時間點狀態(tài)唯一(初始化完成了才能進行增刪改嘛)
ok,了解這些之后,我們來看看怎樣一步步達到目的。
我們以一個需求為例:

就是一個沒有一毛錢特效的Todoist,非常簡單的增刪改查。

初版

按照之前的想法,我們首先需要一份配置文件來描述狀態(tài):

const machine = {
  // 初始狀態(tài)
  initial: "start",
  start: {
    INIT: "loadList"
  },
  loadList: {
    LOAD_LIST_SUCCESS: "showList",
    LOAD_LIST_ERROR: "showListError"
  },
  showListError: {
    RETRY: "loadList"
  },
  showList: {
    ADD: "add",
    EDIT: "edit",
    DELETE: "delete"
  },
  edit: {
    SAVE_EDIT: "saveEdit"
  },
  saveEdit: {
    SAVE_EDIT_SUCCESS: "loadList"
  },
  delete: {
    DELETE_SUCCESS: "loadList"
  },
  add: {
    ADD_SUCCESS: "loadList"
  }
};

配置是寫完了,現(xiàn)在對著上面的需求gif圖說一下這份配置是什么意思。

加載列表數(shù)據(jù)(initial: "start"表示初始狀態(tài)是start,start: {INIT: "loadList"}表示狀態(tài)start觸發(fā)INIT事件之后狀態(tài)會轉(zhuǎn)變成loadList)

加載列表數(shù)據(jù)失敗了(loadList觸發(fā)LOAD_LIST_ERROR事件狀態(tài)轉(zhuǎn)變?yōu)閟howListError)

加載失敗后重新加載(showListError觸發(fā)RETRY事件之后狀態(tài)重新變回loadList

重新加載列表成功(loadList觸發(fā)LOAD_LIST_SUCCESS事件狀態(tài)轉(zhuǎn)變?yōu)閟howList)

列表加載成功就可以對列表進行增刪改操作(showList可以觸發(fā)ADD、DELETE、EDIT事件對應增刪改操作帶來的狀態(tài)變化)

剩下的配置就不繼續(xù)寫了,可以看到通過這份配置,我們可以清晰知道,這份代碼究竟做了些什么,而且寫這份配置有利于整理好自己的思路,讓自己首先將需求過一遍,將所有邊邊角角通過寫配置預演一遍,而不是拿到需求就開擼,遇到了問題才發(fā)現(xiàn)之前寫的代碼不適用。同理如果需求有變,首先從這份配置入手,看看這波修改會對哪些狀態(tài)分支造成影響,就不會出現(xiàn)那種不知道改一個地方會不會影響到別的地方宛如拆炸彈一樣的心情。
接著,為了方便根據(jù)這份配置來進行操作,需要實現(xiàn)一點輔助函數(shù):

class App extends Component {
    constructor(props) {
        state = {
            curState: machine.initial
        }
    }
    handleNextState (nextState, action) {
        switch (nextState) {
            case "loadList":
            // 處理loadList的邏輯
            break;
        }
    }
    transition (action) {
        const { curState } = this.state;
        const nextState = machine[curState][action.type];
        if (nextState) {
            this.setState({ curState: nextState }, () =>
                this.handleNextState(nextState, action)
            );
        }
    }
}

基本就是這樣的結(jié)構(gòu),通過this.transition({ type: "INIT" })觸發(fā)一個事件(INIT)將當前狀態(tài)(start)轉(zhuǎn)變成另外一個狀態(tài)(loadList),而handleNextState則處理狀態(tài)轉(zhuǎn)變后的邏輯(當狀態(tài)變成loadList需要去請求接口獲取列表數(shù)據(jù))。通過這樣的方式,我們真正將狀態(tài)管理了起來,因為我們有清晰的配置文件去描述狀態(tài),我們有分層清晰的地方去處理當前狀態(tài)需要處理的邏輯,這就相當于有明確的戰(zhàn)略圖,大家都根據(jù)這份戰(zhàn)略圖各司其職做好自己的本分,這不是將狀態(tài)管理得井井有條嗎?
而且這樣做之后,比較容易規(guī)避一些意外的錯誤,因為任意時間點狀態(tài)唯一這個特點,帶來了狀態(tài)只能從一個狀態(tài)轉(zhuǎn)變到另一個狀態(tài),比如點擊一個按鈕提交,這時的狀態(tài)是提交中,我們經(jīng)常需要去處理用戶重復點擊而導致重復提交的事情:

let isSubmit = false
const submit = () => {
    if (isSubmit === true) return
    isSubmit = true
    toSubmit().then(() => isSubmit = false)
}
submit()

使用有限狀態(tài)機進行管理后就不需要寫這種額外的isSubmit狀態(tài),因為提交中的狀態(tài)只能轉(zhuǎn)變?yōu)樘峤煌瓿伞?br>上面的代碼完整版請點這里。

更進一步

雖然初版對于狀態(tài)的管理更加清晰了一些,但仍然不夠直觀,如果能將配置轉(zhuǎn)化成圖就好了,有圖有真相嘛。心想事成:

不僅有圖可看,還可以逼真地將所有狀態(tài)都預演一遍。這個好東西就是xstate給予我們的,它是一個實現(xiàn)有限狀態(tài)機模型的js庫,感興趣可以去詳看,這里我們只需要按照它的寫法去寫狀態(tài)機的配置,就可以生成出這樣的圖。
看過xstate會發(fā)現(xiàn),里面的東西真不少,其實如果只是想在簡單的項目上用這種模式試試水,卻要把整個庫引進來似乎不太劃算。那,不如自己來擼一個簡化版?
心動不如行動,先分析一下初版有什么不足之處。

沒有將有限狀態(tài)機的模式分離出來,如果不是用react而是用vue就用不了了。

沒有將模式分離出來導致復用性很差,總不能每個地方要用的時候都要寫一次transition等方法吧。

配置項沒寫成xstate的樣子無法使用xstate提供的工具生成圖。

現(xiàn)在首要的任務就是把有限狀態(tài)機的模式抽離出來,順便使用xstate的寫法來寫配置。

const is = (type, val) =>
  Object.prototype.toString.call(val) === "[object " + type + "]";

export class Fsm {
  constructor(stateConfig) {
    // 狀態(tài)描述配置
    this.stateConfig = stateConfig;
    // 當前狀態(tài)
    this.state = stateConfig.initial;
    // 上一個狀態(tài)
    this.lastState = "";
    // 狀態(tài)離開回調(diào)集合
    this.onExitMap = {};
    // 狀態(tài)進入回調(diào)集合
    this.onEntryMap = {};
    // 狀態(tài)改變回調(diào)
    this.handleStateChange = null;
  }

  /**
   * 改變狀態(tài)
   * @param type 行為類型 描述當前狀態(tài)通過該類型的行為轉(zhuǎn)變到另一個狀態(tài)
   * @param arg 轉(zhuǎn)變過程中的額外傳參
   * @returns {Promise}
   */
  transition({ type, ...arg }) {
    const states = this.stateConfig.states;
    const curState = this.state;
    if (!states) {
      throw "states undefined";
    }
    if (!is("Object", states)) {
      throw "states should be object";
    }
    if (
      !states[curState] ||
      !states[curState]["on"] ||
      !states[curState]["on"][type]
    ) {
      console.warn(`transition fail, current state is ${this.state}`);
      return;
    }
    const nextState = states[curState]["on"][type];
    const curStateObj = states[curState];
    const nextStateObj = states[nextState];
    // 狀態(tài)轉(zhuǎn)變的經(jīng)歷
    return (
      Promise.resolve()
        // 狀態(tài)離開
        .then(() =>
          this.handleLifeCycle({
            type: "onExit",
            stateObj: curStateObj,
            arg: { exitState: curState }
          })
        )
        // 狀態(tài)改變
        .then(() => this.updateState({ state: nextState, lastState: curState }))
        // 進入新狀態(tài)
        .then(() =>
          this.handleLifeCycle({
            type: "onEntry",
            stateObj: nextStateObj,
            arg: { state: nextState, lastState: curState, ...arg }
          })
        )
    );
  }

  /**
   * 狀態(tài)改變回調(diào) 只注冊一次
   * @param cb
   */
  onStateChange(cb) {
    cb &&
      is("Function", cb) &&
      !this.handleStateChange &&
      (this.handleStateChange = cb);
  }

  /**
   * 注冊狀態(tài)離開回調(diào)
   * @param type
   * @param cb
   */
  onExit(type, cb) {
    !this.onExitMap[type] && (this.onExitMap[type] = cb);
  }

  /**
   * 注冊狀態(tài)進入回調(diào)
   * @param type
   * @param cb
   */
  onEntry(type, cb) {
    !this.onEntryMap[type] && (this.onEntryMap[type] = cb);
  }

  /**
   * 更新狀態(tài)
   * @param state
   * @param lastState
   */
  updateState({ state, lastState }) {
    this.state = state;
    this.lastState = lastState;
    this.handleStateChange && this.handleStateChange({ state, lastState });
  }

  /**
   * 處理狀態(tài)轉(zhuǎn)變的生命周期
   * @param stateObj
   * @param type onExit/onEntry
   * @param arg
   * @returns {*}
   */
  handleLifeCycle({ stateObj, type, arg }) {
    const cbName = stateObj[type];
    if (cbName) {
      const cb = this[`${type}Map`][cbName];
      if (cb && is("Function", cb)) {
        return cb(arg);
      }
    }
  }

  /**
   * 獲取當前狀態(tài)
   * @returns {*}
   */
  getState() {
    return this.state;
  }

  /**
   * 獲取上一個狀態(tài)
   * @returns {string|*}
   */
  getLastState() {
    return this.lastState;
  }
}

然后這樣使用就好:

const stateConfig = {
  initial: "start",
  states: {
    start: {
      on: {
        INIT: "loadList"
      },
      onExit: "onExitStart"
    },
     loadList: {
      on: {
        LOAD_LIST_SUCCESS: "showList",
        LOAD_LIST_ERROR: "showListError"
      },
      onEntry: "onEntryLoadList"
    }
  }
}
/*
結(jié)果:
1.console.log("onExitStart")
2.console.log("onEntryLoadList")
3.console.log("transition success")
transition以及生命周期函數(shù)onExit、onEntry都支持promise控制異步流程
*/
const fsm = new Fsm(stateConfig);
transition({ type: "INIT"}).then(() => {
  console.log("transition success")
})
fsm.onExit("onExitStart", (data) => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("onExitStart")
      resolve()
    }, 1000)
  })
})
fsm.onEntry("onEntryLoadList", (data) => {
  console.log("onEntryLoadList")
})

總算把有限狀態(tài)機抽成一個工具來使用了,已經(jīng)完成了最關(guān)鍵的一步。

集成到react去使用

如果想在react中使用,想到比較方便的使用形式是高階組件,需要用到有限狀態(tài)機的組件傳進高階組件,就立馬擁有了使用有限狀態(tài)機的能力。

import React from "react";
import { Fsm } from "../fsm";
export default function(stateConfig) {
  const fsm = new Fsm(stateConfig);
  return function(Component) {
    return class extends React.Component {
      constructor() {
        super();
        this.state = {
          machineState: {
            // 當前狀態(tài)
            value: stateConfig.initial,
            // 上一個狀態(tài)
            lastValue: ""
          }
        };
      }
      updateMachineState(data) {
        this.setState({
          machineState: { ...this.state.machineState, ...data }
        });
      }
      componentDidMount() {
        this.handleStateChange();
        this.handleEvent();
      }

      /**
       * 處理狀態(tài)更新
       */
      handleStateChange() {
        fsm.onStateChange(({ state, lastState }) => {
          this.updateMachineState({ value: state, lastValue: lastState });
        });
      }

      /**
       * 處理狀態(tài)改變事件
       */
      handleEvent() {
        const states = stateConfig.states;
        // 獲取狀態(tài)配置中所有的onEntry與onExit
        const eventObj = Object.keys(states).reduce(
          (obj, key) => {
            const value = states[key];
            const onEntry = value.onEntry;
            const onExit = value.onExit;
            onEntry && obj.onEntry.push(onEntry);
            onExit && obj.onExit.push(onExit);
            return obj;
          },
          {
            onEntry: [],
            onExit: []
          }
        );
        // 獲取組件實例中onEntry與onExit的回調(diào)方法
        Object.keys(eventObj).forEach(key => {
          eventObj[key].forEach(item => {
            this.ref[item] && fsm[key](item, this.ref[item].bind(this.ref));
          });
        });
      }
      render() {
        return (
           (this.ref = c)}
            {...this.state}
            transition={fsm.transition.bind(fsm)}
          />
        );
      }
    };
  };
}

使用的時候就可以:

const stateConfig = {
  initial: "start",
  states: {
    start: {
      on: {
        INIT: "loadList"
      },
      onExit: "onExitStart"
    }
  }
}
class App extends Component {
    componentDidMount () {
        this.props.transition({ type: "INIT" });
    }
    onExitStart () {
        console.log("onExitStart ")
    }
}
export default withFsm(machine)(App);

現(xiàn)在我們可以愉快地使用這個高階組件將Todoist重構(gòu)一遍。
當然,大佬們會說了,我的項目比較復雜,有沒有比較完善的解決方案呢?那肯定是有的,可以看看react-automata,將xstate集成到react中使用。由于我們上面的小高階組件用法比較像react-automata,所以基本不需要什么改動,就可以遷移到react-automata,使用react-automata再重構(gòu)一遍Todoist。

最后

對于符合有限狀態(tài)機的使用場景,使用它確實能將狀態(tài)管理起來,因為我們的狀態(tài)再也不是那種如isSubmit = false/true那樣雜亂無章的狀態(tài),而是某個時間節(jié)點里的一個總括狀態(tài)。不管怎樣,有限狀態(tài)機的方案還是促使了我們?nèi)ブ匦滤伎荚鯓幽芨蟪潭鹊靥岣唔椖康目删S護性,提供了一個新方向盡可能減少祖?zhèn)鞔a,改起bug或者需求的時候分析起來更加容易,終極目的只有一個,那就是,希望能早點下班。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97303.html

相關(guān)文章

  • 狀態(tài)決定視圖——基于狀態(tài)的前端開發(fā)思考

    摘要:前端與狀態(tài)現(xiàn)在的前端開發(fā)中,對于狀態(tài)的管理是重中之重。有限狀態(tài)機那么如何更好的管理前端軟件的復雜度的狀態(tài)機思想給出了自己的答案。有限狀態(tài)機并不是一個復雜的概念簡單說,它有三個特征狀態(tài)總數(shù)是有限的。 前提 在現(xiàn)在的前端社區(qū),關(guān)于MVVM、Model driven view 之類的概念,已經(jīng)算是非常普及了。React/Vue 這類框架可以算是代表。而自己雖然有 React/Vue 的使用經(jīng)...

    miya 評論0 收藏0
  • 前端狀態(tài)管理請三思

    摘要:它們是單向數(shù)據(jù)流和狀態(tài)容器,而不是狀態(tài)管理。幾個月之前我開始尋找可以解決狀態(tài)管理問題的模式,最終我發(fā)現(xiàn)了狀態(tài)機的概念。狀態(tài)機不接受沒有明確定義的輸入作為當前的狀態(tài)。狀態(tài)機強制開發(fā)者以聲明式的方式思考。 最近我開始思考React應用的狀態(tài)管理。我已經(jīng)取得一些有趣的結(jié)論,并且在這篇文章里我會向你展示我們所謂的狀態(tài)管理并不是真的在管理狀態(tài)。 譯者:阿里云前端-也樹 原文鏈接:managing...

    魏憲會 評論0 收藏0
  • 【友盟+】首創(chuàng)SDK自動化測試框架,解決SDK測試痛點

    摘要:相對的測試方案,市面上已經(jīng)有非常多且成熟的級別的自動化測試框架,卻鮮有針對提供的自動化測試方案,原因是屬于為提供服務的插件。 作者:【友盟+】高級無線開發(fā)工程師 吳玉強、王飛 為了確保 SDK 線上運行的穩(wěn)定性,我們需要在開發(fā)后進行 SDK 測試,而為了提高測試效率,而且在拓展新項目的同時能兼顧已有項目的穩(wěn)定性,在有限的資源內(nèi)解放測試人員到更緊急的項目中來,就需要一個自動化工具來完成工...

    lx1036 評論0 收藏0
  • 聽飛狐聊JavaScript設計模式系列13

    摘要:介一回聊狀態(tài)模式,官方描述允許一個對象在其內(nèi)部狀態(tài)改變時改變它的行為。有限狀態(tài)機有限狀態(tài)機是一個非常有用的模型,可以模擬世界上大部分事物。這個是官方說法,簡單說,她有三個特征,狀態(tài)總數(shù)是有限的。,任一時刻,只處在一種狀態(tài)之中。 本回內(nèi)容介紹 上一回聊了聊組合模式(Composite),用組合模式模擬了個圖片庫,聊了遞歸。介一回聊狀態(tài)模式(State),官方描述允許一個對象在其內(nèi)部狀態(tài)改...

    linkin 評論0 收藏0
  • 狀態(tài)引擎選型

    摘要:狀態(tài)機引擎選型概念有限狀態(tài)機是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內(nèi)所經(jīng)歷的狀態(tài)序列,以及如何響應來自外界的各種事件。狀態(tài)機的要素狀態(tài)機可歸納為個要素,即現(xiàn)態(tài)條件動作次態(tài)。 狀態(tài)機引擎選型 date: 2017-06-19 15:50:18 概念 有限狀態(tài)機是一種用來進行對象行為建模的工具,其作用主要是描述對象在它的生命周期內(nèi)所經(jīng)歷的狀態(tài)序列,以及如何響應...

    caige 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<