Skip to content
6 minute read

使用 XState 和 React 管理全局状态

David Khourshid

XState 是一个多功能的状态管理和编排库,可以与任何框架一起使用,包括与 @xstate/react 包一起使用的 React。对于许多应用程序来说,管理全局状态是一个要求,有很多选项可以在 React 中共享全局状态,比如使用 React Context 或者像 ReduxMobXZustand 这样的库。

@xstate/react 包使得使用 useMachine()useActor() 这样的钩子来管理组件级状态变得简单,但它同样适用于管理全局状态 🌎

快速开始

  1. 创建全局逻辑。这可以是一个简单的 promise 或函数,或者是一个复杂的状态机或状态图。
  2. 从该逻辑创建一个 actor 并导出它
  3. 从任何组件中导入该 actor 并:
    • 使用 useSelector(…) 钩子读取 actor 的快照
    • 调用 actorRef.send(…) 发送事件给它。

就是这样!

import { setup, createActor } from 'xstate';
import { useSelector } from '@xstate/react';

// 全局应用逻辑
import { globalLogic } from './globalLogic';

// 创建 actor
export const globalActor = createActor(globalLogic);
// 启动 actor
globalActor.start();

export function App() {
// 读取 actor 的快照
const user = useSelector(globalActor, (snapshot) => snapshot.context.user);

return (
<div>
<h1>你好, {user.name}!</h1>
// 发送事件给 actor
<button onClick={() => globalActor.send({ type: 'logout' })}>
登出
</button>
</div>
);
}

全局状态

管理全局状态的最简单方法是共享一个actor实例。可以将 actor 视为一个“存储”——你可以订阅它的状态更新(“快照”)并向它发送事件。

你可以通过将 actor 作为 prop 传递或直接从模块范围引用来在任何组件中使用它。

import { setup, createActor } from 'xstate';
import { useSelector } from '@xstate/react';

// 全局应用逻辑
const countMachine = createMachine({
context: { count: 0 },
on: {
inc: {
actions: assign({ count: ({ context }) => context.count + 1 })
}
}
});

// 全局 actor - 应用逻辑的一个实例
export const countActor = createActor(countMachine);
countActor.start(); // 立即启动 actor

export function App() {
// 读取 actor 的快照
const count = useSelector(countActor, (state) => state.context.count);

return (
// 发送事件给 actor
<button onClick={() => countActor.send({ type: 'inc' })}>
Count: {count}
</button>
);
}

你可以从任何组件中读取这个全局 actor(“存储”)并向它发送事件:

import { countActor } from './countActor';

export function Counter() {
const count = useSelector(countActor, (state) => state.context.count);

return (
<button onClick={() => countActor.send({ type: 'inc' })}>
Current count: {count}
</button>
);
}

副作用和生命周期

actor 不仅限于作为状态存储。它们还可以用于管理副作用,比如 HTTP 请求,或者从 actor 内部触发副作用。因此,你可能不希望 actor 立即启动。你可以使用 .start() 方法在适当的时间启动 actor,比如在应用挂载时。

import { effectfulActor } from './effectfulActor';

export function App() {
useEffect(() => {
effectfulActor.start();
}, [effectfulActor]);

// ...
}

同样,你也可以通过调用 actor.stop() 来控制 actor 何时停止。

使用 React Context 管理全局状态

如果你更喜欢使用 React Context 来共享全局状态,你可以将上述模式适配为使用 React Context 提供者和消费者。

import { createContext } from 'react';

const someMachine = createMachine(/* ... */);
const someActor = createActor(someMachine);

// 不要忘记启动 actor!
someActor.start();

// 将 `someActor` 传递给 `createContext` 主要是为了类型安全
export const SomeActorContext = createContext(someActor);

export function App() {
return (
<SomeActorContext.Provider value={someActor}>
<Counter />
</SomeActorContext.Provider>
);
}
import { useContext } from 'react';
import { useSelector } from '@xstate/react';
import { SomeActorContext } from './SomeActorContext';

export function Counter() {
const someActor = useContext(SomeActorContext);
const count = useSelector(someActor, (state) => state.context.count);

return (
<button onClick={() => someActor.send({ type: 'inc' })}>
Current count: {count}
</button>
);
}

不仅仅是状态机

状态机非常强大且有用,但有时你不需要所有这些功能。XState v5 使你能够创建不同类型的 actor 逻辑以适应你的用例,例如基于 promise、observable、转换函数、回调等的 actor 逻辑。

import { fromTransition, createActor } from 'xstate';
import { useSelector } from '@xstate/react'

// 演员逻辑
const counterLogic = fromTransition((state, event) => {
if (event.type === 'inc') {
return { count: state.count + 1 };
}
return state;
}, { count: 0 });

const counterActor = createActor(counterLogic);
counterActor.start();

// Same API
export function Counter() {
const count = useSelector(counterActor, (state) => state.context.count);

return (
<button onClick={() => counterActor.send({ type: 'inc' })}>
Current count: {count}
</button>
);
}