React中替换已初始化状态机失败:如何正确更新状态机?
在React中是否可以替换已初始化的State Machine?
可以替换,但你的实现方式有问题——useMachine钩子默认只会在组件挂载时初始化一次状态机服务,后续即使传入的machine参数变化,也不会自动重新创建服务,导致返回的current始终是初始服务的状态。
问题原因
你在Provider中用useState存储stateMachine,然后直接调用useMachine(stateMachine)。但useMachine内部会缓存首次创建的状态机服务,不会响应machine参数的更新。所以即使你通过updateMachine修改了stateMachine的值,useMachine返回的还是基于初始initMachine的服务,current自然不会变化。
解决方案
要实现动态切换状态机,需要手动监听stateMachine的变化,销毁旧服务并创建新服务,同时订阅新服务的状态更新。以下是具体实现:
Provider修改后的代码
import { createMachine, interpret, Interpreter } from 'xstate'; import { useState, useRef, useEffect, useCallback } from 'react'; const dummyConfig = { id: 'init', initial: 'init_state', states: { init_state: { on: {}, }, }, }; const initMachine = createMachine(dummyConfig); const MachineProvider = ({ children }) => { const [stateMachine, setStateMachine] = useState(initMachine); const [current, setCurrent] = useState(initMachine.initialState); const serviceRef = useRef<Interpreter | null>(null); useEffect(() => { // 停止旧服务(如果存在) if (serviceRef.current) { serviceRef.current.stop(); } // 创建新的状态机服务 const newService = interpret(stateMachine); serviceRef.current = newService; // 订阅状态变化,更新current const subscription = newService.subscribe((state) => { setCurrent(state); }); // 启动新服务,若需继承旧状态可传入current:newService.start(current) newService.start(); // 清理函数:销毁服务和订阅 return () => { subscription.unsubscribe(); newService.stop(); serviceRef.current = null; }; }, [stateMachine]); // 封装send方法,指向当前服务 const send = useCallback((event) => { serviceRef.current?.send(event); }, []); const updateMachine = useCallback((machine) => { setStateMachine(machine); }, []); return ( <MachineContext.Provider value={{ current, send, service: serviceRef.current, updateMachine }}> {children} </MachineContext.Provider> ); };
关键改动说明
- 手动管理状态机服务:用
useRef保存当前的状态机服务,避免每次渲染重新创建。 - 监听stateMachine变化:通过
useEffect监听stateMachine的更新,每次变化时销毁旧服务、创建新服务并启动。 - 订阅状态更新:新服务启动后,订阅其状态变化,实时更新
current状态。 - 封装send方法:保证
send始终指向当前活跃的状态机服务。
子组件调用说明
子组件代码无需大改,调用updateMachine传入新创建的状态机即可:
const newConfig = { id: 'new-machine', initial: 'loaded', states: { loaded: { on: { ACTION: 'processing' } }, processing: {} } }; const machine = createMachine(newConfig); const { current, send, updateMachine } = useMachineContext(); // 切换状态机 updateMachine(machine);
可选优化:状态继承
如果需要在切换状态机时保留旧状态(比如携带之前的上下文数据),可以在启动新服务时传入旧状态:
newService.start(current);
内容的提问来源于stack exchange,提问作者duduwe
相关产品推荐
相关产品推荐

