前端React开发中被忽视的设计模式:状态机其实没你想的那么复杂
不是什么新技术,就是那个计算机课上学过的状态机。但用好了,能让你的代码清爽不少。
前段时间重构一个表单组件,发现代码里有这样的状态管理:
const [isLoading, setIsLoading] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [hasError, setHasError] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [showModal, setShowModal] = useState(false);
看起来还行,但问题在于这些状态之间的关系完全靠开发者自觉维护。比如isLoading为true时,isEditing应该是false,但代码里没有强制约束。
结果就是偶尔会出现一些奇怪的组合:按钮既在加载又可以编辑,或者错误提示和成功提示同时出现。
状态机听起来很学术,但核心思想很简单:明确定义所有可能的状态,以及状态之间如何转换。
用状态机重写上面的逻辑:
const formMachine = createMachine({
id: 'form',
initial: 'idle',
states: {
idle: {
on: { EDIT: 'editing', SUBMIT: 'loading' }
},
editing: {
on: { SAVE: 'loading', CANCEL: 'idle' }
},
loading: {
on: { SUCCESS: 'success', ERROR: 'error' }
},
success: {
on: { RESET: 'idle' }
},
error: {
on: { RETRY: 'loading', RESET: 'idle' }
}
}
});
好处很明显:
不是所有场景都需要状态机,我的经验是:
适合用的场景:
不太需要的场景:
如果要在项目中使用状态机,XState是个不错的选择:
import { useMachine } from '@xstate/react';
function FormComponent() {
const [state, send] = useMachine(formMachine);
return (
<div>
{state.matches('loading') && <LoadingSpinner />}
{state.matches('error') && <ErrorMessage />}
<button
onClick={() => send('SUBMIT')}
disabled={!state.can('SUBMIT')}
>
{state.matches('loading') ? '提交中...' : '提交'}
</button>
</div>
);
}
优点:
缺点:
如果觉得XState太重,也可以自己实现简单版本:
function useStateMachine(config) {
const [state, setState] = useState(config.initial);
const send = (event) => {
const currentState = config.states[state];
const nextState = currentState.on[event];
if (nextState) {
setState(nextState);
}
};
return [state, send];
}
这样既能享受状态机的好处,又不会引入太多复杂度。
对于个人项目:试试看,特别是有复杂状态逻辑的时候。即使只是在纸上画个状态图,也能帮你理清思路。
对于团队项目:可以从小范围开始,比如重构一个比较复杂的表单组件。让团队感受一下效果,再决定是否大规模采用。
对于新手:不用急着上手,先把基础的状态管理搞熟练。但可以了解一下这个思路,对理解复杂状态逻辑有帮助。
状态机不是什么银弹,但确实是个有用的工具。特别是当你的组件状态开始变得复杂,各种if-else嵌套让人头疼的时候。
关键是要根据实际情况选择。简单的逻辑用useState就够了,复杂的状态管理可以考虑状态机。
你在项目中遇到过复杂的状态管理问题吗?欢迎在评论区分享你的解决方案!
🔖 如果觉得有用,欢迎关注。下期聊聊"前端性能优化的几个实用技巧"