以下是简单的状态机设计模式和简单示例,核心是先理解状态机的核心思想,再通过极简示例掌握用法,最后扩展到通用设计模式。
一、先搞懂:状态机的核心思想(人话版)
状态机本质是「把复杂的状态切换规则写死,让程序只能按规则走」,就像:
- 玩游戏时,角色「站立」只能切「行走/跳跃」,不能直接切「死亡」;
- 点外卖时,「待付款」只能切「付款中/取消」,不能直接切「已收货」。 核心三要素(记住这3个,状态机就懂了):
- 状态(State): 程序当前的“身份”(比如:空闲、加载中、成功、失败);
- 事件(Event):触发状态变化的“动作”(比如:点击“加载”、请求成功、请求失败);
- 规则(Transition):“什么状态下触发什么事件,能切到什么新状态”(比如:空闲→点击加载→加载中)。
二、前端状态机的通用设计模式(极简版)
不用依赖任何库,纯原生JS就能实现,核心结构就4部分:
// 状态机设计模式模板(通用)
const StateMachine = {
// 1. 初始状态
currentState: "初始状态",
// 2. 状态切换规则:{ 当前状态: { 事件: 目标状态 } }
rules: {},
// 3. 触发状态切换的核心方法
trigger(event, payload) {
// 第一步:查规则,判断当前状态能不能触发这个事件
const nextState = this.rules[this.currentState]?.[event];
if (!nextState) {
console.warn(`❌ ${this.currentState}状态下不能触发${event}事件`);
return;
}
// 第二步:记录旧状态,切换到新状态
const oldState = this.currentState;
this.currentState = nextState;
// 第三步:执行状态切换后的逻辑(比如更新UI、调接口)
this.onChange(nextState, oldState, payload);
},
// 4. 状态变更后的回调(自定义业务逻辑)
onChange(newState, oldState, payload) {
console.log(`✅ 状态变更:${oldState} → ${newState}`, payload);
},
};
三、最简单示例:
按钮点击的加载状态机 用「按钮点击请求数据」这个前端最常见的场景,实现状态机,一看就懂:
需求
按钮有4个状态:
- 初始:
idle(空闲,可点击); - 点击后:
loading(加载中,禁用按钮); - 请求成功:
success(显示成功,3秒后重置为空闲); - 请求失败:
error(显示失败,可重新点击)。
完整代码(可直接复制运行)
<!DOCTYPE html>
<html>
<body>
<!-- UI:一个按钮 + 状态提示 -->
<button id="btn">点击加载数据</button>
<div id="status">当前状态:空闲</div>
<script>
// 1. 初始化状态机
const loadDataFSM = {
// 初始状态:空闲
currentState: "idle",
// 2. 定义状态切换规则(核心!)
rules: { idle: { click: "loading" },
// 空闲→点击→加载中
loading: { success: "success", fail: "error" },
// 加载中→成功/失败
success: { reset: "idle" },
// 成功→重置→空闲
error: { retry: "loading" }
// 失败→重试→加载中
},
// 3. 触发状态切换的方法
trigger(event, payload) {
const nextState = this.rules[this.currentState]?.[event];
if (!nextState) {
alert(`当前${this.currentState}状态,不能${event}!`);
return;
}
const oldState = this.currentState;
this.currentState = nextState;
this.onChange(newState, oldState, payload);
},
// 4. 状态变更后更新UI(业务逻辑)
onChange(newState, oldState, payload) {
const btn = document.getElementById("btn");
const status = document.getElementById("status");
// 根据新状态更新UI
switch (newState) {
case "loading":
btn.disabled = true;
btn.innerText = "加载中...";
status.innerText = "当前状态:加载中";
// 模拟异步请求
mockRequest().then(() => {
this.trigger("success"); // 请求成功→切换到success
}).catch(() => {
this.trigger("fail"); // 请求失败→切换到error
});
break;
case "success":
btn.disabled = false;
btn.innerText = "点击加载数据";
status.innerText = "当前状态:成功(3秒后重置)"; // 3秒后重置为空闲
setTimeout(() => this.trigger("reset"), 3000);
break;
case "error":
btn.disabled = false;
btn.innerText = "重新加载";
status.innerText = "当前状态:失败,可重试";
break;
case "idle":
btn.innerText = "点击加载数据";
status.innerText = "当前状态:空闲";
break;
}
}
};
// 模拟异步请求(50%成功率)
function mockRequest() {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > 0.5 ? resolve() : reject();
}, 1000);
});
}
// 绑定按钮点击事件
document.getElementById("btn").addEventListener("click", () => {
// 点击按钮:触发对应事件(idle态触发click,error态触发retry)
const event = loadDataFSM.currentState === "idle" ? "click" : "retry";
loadDataFSM.trigger(event);
});
</script>
</body>
</html>
运行效果
- 初始:按钮可点击,显示“点击加载数据”,状态提示“空闲”;
- 点击按钮:按钮禁用,显示“加载中...”,1秒后随机切换:
- 成功:按钮恢复,显示“点击加载数据”,3秒后重置为初始态;
- 失败:按钮恢复,显示“重新加载”,点击可重试;
- 非法操作拦截:比如“加载中”时重复点击按钮,会提示“加载中状态下不能click事件”。
四、核心设计思路拆解(通俗易懂)
1. 为什么这么设计?
- 把「状态规则」和「业务逻辑」分开:规则写在
rules里,UI逻辑写在onChange里,不用混在一起; - 所有状态切换都走
trigger方法,相当于“唯一入口”,避免直接改currentState导致的混乱; - 规则是“显性的”,比如看
rules就知道:
rules: {
idle: { click: "loading" },
loading: { success: "success", fail: "error" },
success: { reset: "idle" },
error: { retry: "loading" }
}
不用逐行读代码,就能知道“什么状态能做什么事”。
2. 关键细节(新手必看)
trigger方法是“门卫”:先查规则,再切换状态,最后执行逻辑,一步都不能少;onChange方法是“执行者”:只负责状态变更后的业务(比如更UI、调接口),不关心怎么切换的;- 永远不要直接改
currentState:比如不要写loadDataFSM.currentState = "success",必须通过trigger触发,否则会绕过规则校验。
五、扩展:更通用的状态机类(可复用)
如果多个场景要用状态机,可封装成类,复用性更高:
// 通用状态机类
class SimpleFSM {
constructor(initialState, rules, onChange) {
this.currentState = initialState; // 初始状态
this.rules = rules; // 切换规则
this.onChange = onChange; // 状态变更回调
}
// 触发状态切换
trigger(event, payload) {
const nextState = this.rules[this.currentState]?.[event];
if (!nextState) {
console.warn(`❌ 非法操作:${this.currentState} → ${event}`);
return false;
}
const oldState = this.currentState;
this.currentState = nextState;
this.onChange?.(nextState, oldState, payload); // 执行回调
return true;
}
// 获取当前状态
getState() {
return this.currentState;
}
}
// 用法:创建一个弹窗状态机
const modalFSM = new SimpleFSM(
"closed", // 初始状态:关闭
// 切换规则
{
closed: { open: "opened" },
opened: { close: "closed", confirm: "closed" }
},
// 状态变更回调(更新弹窗UI)
(newState) => {
const modal = document.getElementById("modal");
modal.style.display = newState === "opened" ? "block" : "none";
}
);
// 调用示例
modalFSM.trigger("open"); // 打开弹窗
modalFSM.trigger("confirm");// 确认关闭弹窗
modalFSM.trigger("open"); // 非法操作:closed态才能open,opened态不行,会提示
总结(关键点回顾)
- 状态机核心三要素:状态(当前身份)、事件(触发动作)、规则(切换逻辑);
- 设计模式核心:一个唯一的切换入口(
trigger)+ 显性的规则(rules)+ 分离的业务逻辑(onChange); - 核心好处:拦截非法操作、避免状态混乱、逻辑清晰易维护,哪怕是简单场景也能少写bug。 这个设计模式不用依赖任何第三方库,纯原生JS就能实现,新手也能快速上手,是前端状态机最基础、最易懂的写法。