如何使用 XState 和 React 管理全局状态

2025-06-09

如何使用 XState 和 React 管理全局状态

本文已成为官方XState 文档的一部分!

许多 React 应用程序遵循由Redux推广的 Flux 架构。这种架构可以通过以下几个关键思想来体现:

  1. 它使用应用程序顶部的单个对象来存储所有应用程序状态,通常称为存储
  2. 它提供了一个dispatch函数,可用于将消息发送到 store。Redux 调用了这些方法actions,但我将直接调用它们events——因为它们在 XState 中是已知的。
  3. 商店如何响应来自应用程序的这些消息以纯函数来表达 - 最常见的是使用reducer

本文不会深入探讨 Flux 架构是否可行。David Khourshid 的文章《Redux 只是一种模式的一半》对此进行了详细的阐述。为了方便理解,我们假设您喜欢全局存储,并且希望在 XState 中复制它。

这样做有很多原因。在管理复杂的异步行为和建模难题方面,XState 是首屈一指的。在 Redux 应用中,管理这些功能通常需要中间件:redux-thunkredux-loopredux-saga。选择 XState 可以让你以一流的方式管理复杂性。

全球可用的商店

为了模拟 Redux 的全局可用 store,我们将使用 React context。React context 的使用可能比较棘手——如果传入的值变化过于频繁,可能会导致整个组件树都重新渲染。这意味着我们需要传入变化尽可能小的值。

幸运的是,XState 为我们提供了一流的方法来实现这一点。

import React, { createContext } from 'react';
import { useInterpret } from '@xstate/react';
import { authMachine } from './authMachine';
import { ActorRefFrom } from 'xstate';

interface GlobalStateContextType {
  authService: ActorRefFrom<typeof authMachine>;
}

export const GlobalStateContext = createContext(
  // Typed this way to avoid TS errors,
  // looks odd I know
  {} as GlobalStateContextType,
);

export const GlobalStateProvider = (props) => {
  const authService = useInterpret(authMachine);

  return (
    <GlobalStateContext.Provider value={{ authService }}>
      {props.children}
    </GlobalStateContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

使用useInterpret会返回一个service,它是正在运行的机器的静态引用,可以订阅它。这个值永远不会改变,所以我们不必担心浪费重新渲染的时间。

利用上下文

沿着树往下看,您可以像这样订阅服务:

import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';
import { useActor } from '@xstate/react';

export const SomeComponent = (props) => {
  const globalServices = useContext(GlobalStateContext);
  const [state] = useActor(globalServices.authService);

  return state.matches('loggedIn') ? 'Logged In' : 'Logged Out';
};
Enter fullscreen mode Exit fullscreen mode

useActor钩子监听服务的每次变化并更新其state值。

提高性能

上面的实现存在一个问题——服务的任何变更都会更新组件。Redux 提供了使用选择器获取状态的工具——选择器是用来限制哪些状态部分会导致组件重新渲染的函数。

幸运的是,XState 也提供了该功能。

import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';
import { useSelector } from '@xstate/react';

const selector = (state) => {
  return state.matches('loggedIn');
};

export const SomeComponent = (props) => {
  const globalServices = useContext(GlobalStateContext);
  const isLoggedIn = useSelector(globalServices.authService, selector);

  return isLoggedIn ? 'Logged In' : 'Logged Out';
};
Enter fullscreen mode Exit fullscreen mode

现在,此组件仅在返回不同的值时才会重新渲染。如果您想要优化性能,state.matches('loggedIn')我推荐这种方法。useActor

调度事件

要将事件分派到全局存储,您可以直接调用服务的send功能。

import React, { useContext } from 'react';
import { GlobalStateContext } from './globalState';

export const SomeComponent = (props) => {
  const globalServices = useContext(GlobalStateContext);

  return (
    <button onClick={() => globalServices.authService.send('LOG_OUT')}>
      Log Out
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

请注意,您不需要useActor为此调用,它可以直接在上下文中使用。

与 Flux 的偏差

眼尖的读者可能会发现,这个实现与 Flux略有不同。例如,它不是使用单个全局存储,而是同时运行多个机器:authServicedataCacheServiceglobalTimeoutService。它们每个都有自己的send属性,因此你调用的不是全局调度。

这些变化是可以解决的。可以send在全局存储中创建一个合成函数,send手动调用所有服务的函数。但就我个人而言,我更喜欢确切地知道我的消息会传递给哪些服务,这样可以避免将事件保留在全局命名空间中。

概括

XState 可以完美地用作 React 应用程序的全局存储。它使应用程序逻辑保持共置,将副作用视为“一等公民”,并提供良好的性能useSelector。如果您热衷于 Flux 架构,但又觉得应用程序的逻辑有些失控,那么您应该选择这种方法。

鏂囩珷鏉ユ簮锛�https://dev.to/mattpocockuk/how-to-manage-global-state-with-xstate-and-react-3if5
PREV
“只使用 Props”:React 和 XState 的指南
NEXT
坚持终有回报:React 组件与本地存储同步