html本身无内置状态机,所谓“html状态机”实为javascript控制dom元素状态响应,核心是用map+switch手写fsm控制器,配合data-属性驱动样式与行为,复杂场景再引入xstate。

什么是 HTML 里能用的“状态机”
HTML 本身没有内置状态机(FSM)机制,state 不是标准属性,<template></template> 也不自动跟踪状态流转。所谓“HTML 做状态机”,本质是用 JavaScript 控制一组预定义状态,并让 DOM 元素(比如 <button></button>、<div>)的可见性、类名、禁用状态等随状态变化而响应——HTML 只负责承载视图,状态逻辑必须由 JS 实现。
<h3>用 <code>Map + switch 手写最简 FSM 控制器
别一上来就引入 XState 或 Robot 框架。多数表单/步骤流程(如注册页的 idle → validating → success)用原生 JS 就够了,关键是把转移规则显式写出来:
const fsm = {
state: 'idle',
transitions: {
idle: { validate: 'validating' },
validating: { success: 'success', error: 'idle' },
success: { reset: 'idle' }
},
send(event) {
const next = this.transitions[this.state]?.[event];
if (next !== undefined) {
this.state = next;
this.render();
}
},
render() {
document.body.className = `state-${this.state}`;
// 或操作具体元素:document.querySelector('[data-state-target]').dataset.state = this.state;
}
};
常见错误:
- 把事件名写成字符串字面量(如
'click')硬编码进transitions,导致无法复用;应统一用业务语义名(如'validate'),再在事件监听里映射:button.addEventListener('click', () => fsm.send('validate')) - 忘记校验
next是否存在,直接赋值导致状态变成undefined -
render()里直接innerHTML = ...覆盖整个区域,破坏已绑定的事件监听器
用 data- 属性驱动状态样式与行为
HTML 配合 FSM 的核心技巧是把状态“外显化”,而不是藏在 JS 变量里。这样 CSS 和测试都更可靠:
例如,在根容器上设置:<main data-fsm-state="validating"></main>,然后写 CSS:
[data-fsm-state="validating"] .submit-btn { opacity: 0.6; pointer-events: none; }
[data-fsm-state="success"] .result-panel { display: block; }
JS 更新时只需一行:document.documentElement.dataset.fsmState = fsm.state(注意 kebab-case 自动转 camelCase)。
关键点:
- 避免用
class存状态(如class="state-validating"),因为多个 class 共存时容易冲突;data-是专用命名空间 - 不要用
style.display = 'none'控制显隐——它会覆盖 CSS 媒体查询或动画,优先用hidden属性或[data-fsm-state="x"] > .el选择器 - 服务端渲染(SSR)页面首次加载时,可从 HTML 中读取初始
data-fsm-state,避免闪屏
什么时候该换 XState 而不是手写
当出现以下任一情况,手写 FSM 就开始失控:
- 状态图里有嵌套子状态(比如
editing.draft/editing.published) - 需要并行状态(如同时跟踪
auth: logged-in和ui: sidebar-open) - 要记录状态变迁日志、做时间旅行调试、或生成可视化状态图
- 团队协作中多人修改同一状态逻辑,手写
if/else易出错且难评审
此时用 XState,定义清晰可验证:
import { createMachine } from 'xstate';
const machine = createMachine({
id: 'form',
initial: 'idle',
states: {
idle: { on: { VALIDATE: 'validating' } },
validating: { on: { RESOLVE: 'success', REJECT: 'idle' } },
success: { on: { RESET: 'idle' } }
}
});
但注意:XState 默认不操作 DOM,你仍需监听 service.onTransition 并手动更新 data-fsm-state 或调用 render() —— 它管状态逻辑,不管 HTML 绑定。
真正容易被忽略的是:状态机的“动作”(actions)不该直接操作 DOM 元素(比如 document.getElementById('msg').innerText = 'OK'),而应触发可测试的纯函数,再由顶层协调器决定如何渲染。否则状态逻辑和视图耦合,后续改用 Web Components 或框架时就得重写一半。











