JS前端實現fsm有限狀態(tài)機實例詳解
引言
我們平時開發(fā)時本質上就時對應用程序的各種狀態(tài)進行切換并作出相應處理,最直接的方法就是添加標志位然后考慮所有可能出現的邊界問題,通過if...else if...else 來對當前狀態(tài)進行判斷從而達成頁面的交互效果, 但隨著業(yè)務需求的增加各種狀態(tài)也會隨之增多,我們就不得不再次修改if...else代碼或者增加對應的判斷,最終使得程序的可讀性、擴展性、維護性變得很麻煩
有限狀態(tài)機,(英語:Finite-state machine, FSM),又稱有限狀態(tài)自動機,簡稱狀態(tài)機,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉移和動作等行為的數學模型。
利用有限狀態(tài)機我們可以將條件判斷的結果轉化為狀態(tài)對象內部的狀態(tài),并且能夠使用對應的方法,進行對應的改變。這樣方便了對狀態(tài)的管理也會很容易,也是更好的實踐了UI=fn(state)思想。
舉個栗子??
我們這里用一個簡易的紅綠燈案例,實現一個簡易的有限狀態(tài)機,并且可以通過每一個狀態(tài)暴露出來的方法,改變當前的狀態(tài)
const door = machine({
RED: {
yello: "YELLO",
},
GREEN: {
red: "RED",
},
YELLO: {
green: "GREEN",
},
});
- 首先初始時
door的狀態(tài)顯示為紅燈即RED - 當我們進行
yello操作的時候,狀態(tài)變成黃燈,即狀態(tài)改變?yōu)?code>YELLO - 當我們進行
green操作的時候,狀態(tài)變成綠燈,即狀態(tài)改變?yōu)?code>GREEN - 當我們連著進行
red操作、yello操作的時候,最終狀態(tài)變成黃燈,即狀態(tài)改變?yōu)?code>YELLO ...
從零開始
通過接受一個對象(如果是函數就執(zhí)行),拿到初始值,并且在函數內部維護一個變量記錄當前的狀態(tài),并且記錄第一個狀態(tài)為初始狀態(tài)
const machine = statesObject => {
if (typeof statesObject == "function") statesObject = statesObject();
let currentState;
for (const stateName in statesObject) {
currentState = currentState || statesObject[stateName];
}
};
獲取狀態(tài)
因為當前狀態(tài)是通過函數局部變量currentState進行保存,我們需要一些方法
getMachineState:獲取當前的狀態(tài)getMachineEvents:獲取當前狀態(tài)上保存了哪些方法
這兩個函數通過stateMachine進行保存并作為函數結果進行返回
const machine = statesObject => {
let currentState
...
const getMachineState = () => currentState.name;
const getMachineEvents = () => {
const events = [];
for (const property in currentState) {
if (typeof currentState[property] == "function") events.push(property);
}
return events;
};
const stateMachine = { getMachineState, getMachineEvents };
...
return stateMachine
};
狀態(tài)改變
我們進行改變的時候,調用的是一開始配置好的方法對狀態(tài)進行更改,此時需要將每一個狀態(tài)合并到stateStore中進行保存
再將對應的方法作為偏函數(函數預先將轉換的狀態(tài)和方法進行傳遞),保存在stateMachine(stateMachine會作為結果進行返回),這樣就可以
- 使用
.yello()、.red()、.green()的方法,改變狀態(tài) - 使用
.getMachineState()、.getMachineEvents()查看當前狀態(tài)和查看當前狀態(tài)對應的方法
const machine = statesObject => {
if (typeof statesObject == "function") statesObject = statesObject();
let currentState;
const stateStore = {};
const getMachineState = () => currentState.name;
const getMachineEvents = () => {
const events = [];
for (const property in currentState) {
if (typeof currentState[property] == "function") events.push(property);
}
return events;
};
const stateMachine = { getMachineState, getMachineEvents };
for (const stateName in statesObject) {
stateStore[stateName] = statesObject[stateName];
for (const event in stateStore[stateName]) {
stateMachine[event] = transition.bind(null, stateName, event);
}
stateStore[stateName].name = stateName;
currentState = currentState || stateStore[stateName];
}
return stateMachine;
};
transition
上面代碼中最重要的莫過于transition函數,即改變當前狀態(tài),在stateStore中獲取當前的要更改的狀態(tài)名,重新給currentState賦值,并返回stateMachine供函數繼續(xù)鏈式調用
const machine = statesObject => {
...
const transition = (stateName, eventName) => {
currentState = stateStore[stateName][eventName];
return stateMachine;
};
for (const stateName in statesObject) {
stateStore[stateName] = statesObject[stateName];
for (const event in stateStore[stateName]) {
stateMachine[event] = transition.bind(null, stateName, event);
}
stateStore[stateName].name = stateName;
currentState = currentState || stateStore[stateName];
}
return stateMachine;
};
看似沒有問題,但是如果我們按照上面的代碼執(zhí)行后,獲得的狀態(tài)值為undefined,因為我們在getMachineState時,獲取到的是currentState.name,而不是currentState,所以此時在獲取狀態(tài)的時候需要用通過函數進行獲取obj => obj[xxx]
const machine = statesObject => {
...
const transition = (stateName, eventName) => {
currentState = stateStore[stateName][eventName](stateStore);
return stateMachine;
};
for (const stateName in statesObject) {
stateStore[stateName] = statesObject[stateName];
for (const event in stateStore[stateName]) {
const item = stateStore[stateName][event];
if (typeof item == "string") {
stateStore[stateName][event] = obj => obj[item];
stateMachine[event] = transition.bind(null, stateName, event);
}
}
stateStore[stateName].name = stateName;
currentState = currentState || stateStore[stateName];
}
return stateMachine;
};
實現fsm狀態(tài)機
現在我們實現了一個完整的fsm,當我們配置好狀態(tài)機時
const door = machine({
RED: {
yello: "YELLO",
},
GREEN: {
red: "RED",
},
YELLO: {
green: "GREEN",
},
});
執(zhí)行如下操作時,會打印我們想要的結果
door.getMachineState()--> REDdoor.yello().getMachineState()--> YELLOdoor.green().getMachineState()--> GREENdoor.red().yello().getMachineState()--> YELLO
實現鉤子函數
但是我們監(jiān)聽不到狀態(tài)機的改變,所以當我們想監(jiān)聽狀態(tài)變換時,應該從內部暴露出鉤子函數,這樣可以監(jiān)聽到狀態(tài)機內部的變化,又能進行一些副作用操作
對此,可以對transition進行一些改造,將對于currentState狀態(tài)的改變用方法setMachineState去處理
setMachineState函數會攔截當前狀態(tài)上綁定onChange方法進行觸發(fā),并將改變狀態(tài)的函數、改變前的狀態(tài)、改變后的狀態(tài)傳遞出去
const machine = statesObject => {
...
const setMachineState = (nextState, eventName) => {
let onChangeState;
let lastState = currentState;
const resolveSpecialEventFn = (stateName, fnName) => {
for (let property in stateStore[stateName]) {
if (property.toLowerCase() === fnName.toLowerCase()) {
return stateStore[stateName][property];
}
}
};
currentState = nextState;
onChangeState = resolveSpecialEventFn(lastState.name, "onChange");
if (
onChangeState &&
typeof onChangeState == "function" &&
lastState.name != currentState.name
) {
onChangeState.call(
stateStore,
eventName,
lastState.name,
currentState.name
);
}
};
const transition = (stateName, eventName) => {
const curState = stateStore[stateName][eventName](stateStore);
setMachineState(curState, eventName);
return stateMachine;
};
...
return stateMachine;
};
這樣我們在調用時,狀態(tài)的每一次改變都可以監(jiān)聽到,并且可以執(zhí)行對應的副作用函數
const door = machine({
RED: {
yello: "YELLO",
onChange(fn, from, to) {
console.log(fn, from, to, "onChange");
},
},
GREEN: {
red: "RED",
onChange(fn, from, to) {
console.log(fn, from, to, "onChange");
},
},
YELLO: {
green: "GREEN",
onChange(fn, from, to) {
console.log(fn, from, to, "onChange");
},
},
});
完整代碼
export default statesObject => {
if (typeof statesObject == "function") statesObject = statesObject();
let currentState;
const stateStore = {};
const getMachineState = () => currentState.name;
const getMachineEvents = () => {
let events = [];
for (const property in currentState) {
if (typeof currentState[property] == "function") events.push(property);
}
return events;
};
const stateMachine = { getMachineState, getMachineEvents };
const setMachineState = (nextState, eventName) => {
let onChangeState;
let lastState = currentState;
const resolveSpecialEventFn = (stateName, fnName) => {
for (let property in stateStore[stateName]) {
if (property.toLowerCase() === fnName.toLowerCase()) {
return stateStore[stateName][property];
}
}
};
currentState = nextState;
onChangeState = resolveSpecialEventFn(lastState.name, "onChange");
if (
onChangeState &&
typeof onChangeState == "function" &&
lastState.name != currentState.name
) {
onChangeState.call(
stateStore,
eventName,
lastState.name,
currentState.name
);
}
};
const transition = (stateName, eventName) => {
const curState = stateStore[stateName][eventName](stateStore);
setMachineState(curState, eventName);
return stateMachine;
};
for (const stateName in statesObject) {
stateStore[stateName] = statesObject[stateName];
for (const event in stateStore[stateName]) {
const item = stateStore[stateName][event];
if (typeof item == "string") {
stateStore[stateName][event] = obj => obj[item];
stateMachine[event] = transition.bind(null, stateName, event);
}
}
stateStore[stateName].name = stateName;
currentState = currentState || stateStore[stateName];
}
return stateMachine;
};
總結
這個 fsm有限狀態(tài)機 主要完成了:
- 狀態(tài)的可觀測
- 狀態(tài)的鏈式調用
- 狀態(tài)變化的鉤子函數
以上就是JS前端實現fsm有限狀態(tài)機實例詳解的詳細內容,更多關于JS前端fsm有限狀態(tài)機的資料請關注腳本之家其它相關文章!
相關文章
競態(tài)條件Race condition及如何避免的三種方案詳解
這篇文章主要為大家介紹了競態(tài)條件Race condition及如何避免的三種方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10

