# @xstate/react

@xstate/react package (opens new window) 包含将 XState (opens new window)React (opens new window) 结合使用的实用程序。

¥The @xstate/react package (opens new window) contains utilities for using XState (opens new window) with React (opens new window).

[[目录]]

¥

# 快速开始

¥Quick Start

  1. 安装 xstate@xstate/react

    ¥Install xstate and @xstate/react:

npm i xstate @xstate/react

通过 CDN

¥Via CDN

<script src="https://unpkg.com/@xstate/react/dist/xstate-react.umd.min.js"></script>

通过使用全局变量 XStateReact

¥By using the global variable XStateReact

  1. 导入 useMachine 钩子:

    ¥Import the useMachine hook:

import { useMachine } from '@xstate/react';
import { createMachine } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: {
      on: { TOGGLE: 'active' }
    },
    active: {
      on: { TOGGLE: 'inactive' }
    }
  }
});

export const Toggler = () => {
  const [state, send] = useMachine(toggleMachine);

  return (
    <button onClick={() => send('TOGGLE')}>
      {state.value === 'inactive'
        ? 'Click to activate'
        : 'Active! Click to deactivate'}
    </button>
  );
};

# 示例

¥Examples

# API

# useMachine(machine, options?)

React 钩子 (opens new window) 解释给定的 machine 并启动在组件的生命周期内运行的服务。

¥A React hook (opens new window) that interprets the given machine and starts a service that runs for the lifetime of the component.

参数

¥Arguments

  • machine - XState 机器 (opens new window) 或延迟返回机器的函数:

    ¥machine - An XState machine (opens new window) or a function that lazily returns a machine:

    // existing machine
    const [state, send] = useMachine(machine);
    
    // lazily-created machine
    const [state, send] = useMachine(() =>
      createMachine({
        /* ... */
      })
    );
    
  • options(可选) - 解释器选项 (opens new window) 和/或以下任何机器配置选项:guardsactionsservicesdelaysimmediatecontextstate。如果计算机已包含任何这些选项,它们将被合并,并且这些选项优先。

    ¥options (optional) - Interpreter options (opens new window) and/or any of the following machine config options: guards, actions, services, delays, immediate, context, state. If the machine already contains any of these options, they will be merged, with these options taking precedence.

返回 [state, send, service] 的元组:

¥Returns a tuple of [state, send, service]:

  • state - 将机器的当前状态表示为 XState State 对象。

    ¥state - Represents the current state of the machine as an XState State object.

  • send - 将事件发送到正在运行的服务的函数。

    ¥send - A function that sends events to the running service.

  • service - 创建的服务。

    ¥service - The created service.

# useActor(actor, getSnapshot?)

订阅现有 actor (opens new window) 发出的更改的 React 钩子 (opens new window)

¥A React hook (opens new window) that subscribes to emitted changes from an existing actor (opens new window).

参数

¥Arguments

  • actor - 一个类似 actor 的对象,包含 .send(...).subscribe(...) 方法。

    ¥actor - an actor-like object that contains .send(...) and .subscribe(...) methods.

  • getSnapshot - 一个应该从 actor 返回最新发出值的函数。

    ¥getSnapshot - a function that should return the latest emitted value from the actor.

    • 默认尝试从 actor.getSnapshot() 获取快照,如果不存在则返回 undefined

      ¥Defaults to attempting to get the snapshot from actor.getSnapshot(), or returning undefined if that does not exist.

const [state, send] = useActor(someSpawnedActor);

// with custom actors
const [state, send] = useActor(customActor, (actor) => {
  // implementation-specific pseudocode example:
  return actor.getLastEmittedValue();
});

# useInterpret(machine, options?, observer?)

一个 React 钩子,返回从 machineoptions 创建的 service(如果指定)。它启动服务并在组件的生命周期内运行它。这与 useMachine 类似;但是,useInterpret 允许自定义 observer 订阅 service

¥A React hook that returns the service created from the machine with the options, if specified. It starts the service and runs it for the lifetime of the component. This is similar to useMachine; however, useInterpret allows for a custom observer to subscribe to the service.

当你需要细粒度控制时,例如,useInterpret 非常有用。 添加日志记录,或最小化重新渲染。与 useMachine 会将每次更新从机器刷新到 React 组件相比,useInterpret 相反返回一个静态引用(仅到解释机器),当其状态发生变化时,该引用不会重新渲染。

¥The useInterpret is useful when you want fine-grained control, e.g. to add logging, or minimize re-renders. In contrast to useMachine that would flush each update from the machine to the React component, useInterpret instead returns a static reference (to just the interpreted machine) which will not rerender when its state changes.

要在渲染内使用服务中的一段状态,请使用 useSelector(...) 钩子来订阅它。

¥To use a piece of state from the service inside a render, use the useSelector(...) hook to subscribe to it.

从 1.3.0 开始

¥Since 1.3.0

参数

¥Arguments

  • machine - XState 机器 (opens new window) 或延迟返回机器的函数。

    ¥machine - An XState machine (opens new window) or a function that lazily returns a machine.

  • options(可选) - 解释器选项 (opens new window) 和/或以下任何机器配置选项:guardsactionsservicesdelaysimmediatecontextstate。如果计算机已包含任何这些选项,它们将被合并,并且这些选项优先。

    ¥options (optional) - Interpreter options (opens new window) and/or any of the following machine config options: guards, actions, services, delays, immediate, context, state. If the machine already contains any of these options, they will be merged, with these options taking precedence.

  • observer(可选) - 监听状态更新的观察者或监听器:

    ¥observer (optional) - an observer or listener that listens to state updates:

    • 观察员(例如 { next: (state) => {/* ... */} }

      ¥an observer (e.g., { next: (state) => {/* ... */} })

    • 或监听器(例如,(state) => {/* ... */}

      ¥or a listener (e.g., (state) => {/* ... */})

import { useInterpret } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const App = () => {
  const service = useInterpret(someMachine);

  // ...
};

使用选项+监听器:

¥With options + listener:

// ...

const App = () => {
  const service = useInterpret(
    someMachine,
    {
      actions: {
        /* ... */
      }
    },
    (state) => {
      // subscribes to state changes
      console.log(state);
    }
  );

  // ...
};

# useSelector(actor, selector, compare?, getSnapshot?)

一个 React 钩子,从 actor 的快照返回选定的值,例如服务。仅当所选值发生更改(由可选的 compare 函数确定)时,此钩子才会导致重新渲染。

¥A React hook that returns the selected value from the snapshot of an actor, such as a service. This hook will only cause a rerender if the selected value changes, as determined by the optional compare function.

从 1.3.0 开始

¥Since 1.3.0

参数

¥Arguments

  • actor - 包含 .send(...).subscribe(...) 方法的服务或类似参与者的对象。

    ¥actor - a service or an actor-like object that contains .send(...) and .subscribe(...) methods.

  • selector - 一个函数,它将角色的 "当前状态"(快照)作为参数并返回所需的选定值。

    ¥selector - a function that takes in an actor's "current state" (snapshot) as an argument and returns the desired selected value.

  • compare(可选) - 确定当前选择的值是否与先前选择的值相同的函数。

    ¥compare (optional) - a function that determines if the current selected value is the same as the previous selected value.

  • getSnapshot(可选) - 一个应该从 actor 返回最新发出值的函数。

    ¥getSnapshot (optional) - a function that should return the latest emitted value from the actor.

    • 默认尝试从 actor.getSnapshot() 获取快照,如果不存在则返回 undefined。会自动从服务中提取状态。

      ¥Defaults to attempting to get the snapshot from actor.getSnapshot(), or returning undefined if that does not exist. Will automatically pull the state from services.

import { useSelector } from '@xstate/react';

// tip: optimize selectors by defining them externally when possible
const selectCount = (state) => state.context.count;

const App = ({ service }) => {
  const count = useSelector(service, selectCount);

  // ...
};

具有 compare 功能:

¥With compare function:

// ...

const selectUser = (state) => state.context.user;
const compareUser = (prevUser, nextUser) => prevUser.id === nextUser.id;

const App = ({ service }) => {
  const user = useSelector(service, selectUser, compareUser);

  // ...
};

# createActorContext(machine)

从 3.1.0 开始

¥Since 3.1.0

返回一个 React 上下文对象 (opens new window),它解释 machine 并使解释的 actor 通过 React Context 可用。有一些辅助方法用于访问状态和参与者引用。

¥Returns a React Context object (opens new window) that interprets the machine and makes the interpreted actor available through React Context. There are helper methods for accessing state and the actor ref.

参数

¥Arguments

返回

¥Returns

返回一个包含以下属性的 React Context 对象:

¥Returns a React Context object that contains the following properties:

  • Provider - 具有以下属性的 React Context Provider 组件:

    ¥Provider - a React Context Provider component with the following props:

  • useActor() - 一个从 React Context 返回 [state, send] 元组的 React hook

    ¥useActor() - a React hook that returns a tuple of [state, send] from the React Context

  • useSelector(selector, compare?) - 一个 React hook,它接受 selector 函数和可选的 compare 函数并返回从 actor 快照中选择的值

    ¥useSelector(selector, compare?) - a React hook that takes in a selector function and optional compare function and returns the selected value from the actor snapshot

  • useActorRef() - 一个 React hook,返回解释后的 machine 的 actor ref

    ¥useActorRef() - a React hook that returns the actor ref of the interpreted machine

为参与者创建一个 React Context 并在应用范围内提供它:

¥Creating a React Context for the actor and providing it in app scope:

import { createActorContext } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const SomeMachineContext = createActorContext(someMachine);

function App() {
  return (
    <SomeMachineContext.Provider>
      <SomeComponent />
    </SomeMachineContext.Provider>
  );
}

在组件中使用 actor:

¥Consuming the actor in a component:

import { SomeMachineContext } from '../path/to/SomeMachineContext';

function SomeComponent() {
  // Read full snapshot and get `send` function from `useActor()`
  const [state, send] = SomeMachineContext.useActor();

  // Or derive a specific value from the snapshot with `useSelector()`
  const count = SomeMachineContext.useSelector((state) => state.context.count);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => send('INCREMENT')}>Increment</button>
    </div>
  );
}

阅读角色参考:

¥Reading the actor ref:

import { SomeMachineContext } from '../path/to/SomeMachineContext';

function SomeComponent() {
  const actorRef = SomeMachineContext.useActorRef();

  return (
    <div>
      <button onClick={() => actorRef.send('INCREMENT')}>Increment</button>
    </div>
  );
}

提供类似机器:

¥Providing a similar machine:

import { SomeMachineContext } from '../path/to/SomeMachineContext';
import { someMachine } from '../path/to/someMachine';

function SomeComponent() {
  return (
    <SomeMachineContext.Provider
      machine={() =>
        someMachine.withConfig({
          /* ... */
        })
      }
    >
      <SomeOtherComponent />
    </SomeMachineContext.Provider>
  );
}

# 浅比较

¥Shallow comparison

默认比较是严格参考比较 (===)。如果你的选择器返回非原始值(例如对象或数组),你应该记住这一点,要么返回相同的引用,要么提供浅或深的比较器。

¥The default comparison is a strict reference comparison (===). If your selector returns non-primitive values, such as objects or arrays, you should keep this in mind and either return the same reference, or provide a shallow or deep comparator.

shallowEqual(...) 比较器函数可用于浅比较:

¥The shallowEqual(...) comparator function is available for shallow comparison:

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

// ...

const selectUser = (state) => state.context.user;

const App = ({ service }) => {
  // shallowEqual comparator is needed to compare the object, whose
  // reference might change despite the shallow object values being equal
  const user = useSelector(service, selectUser, shallowEqual);

  // ...
};

:::

对于 useInterpret(...)

¥With useInterpret(...):

import { useInterpret, useSelector } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const selectCount = (state) => state.context.count;

const App = () => {
  const service = useInterpret(someMachine);
  const count = useSelector(service, selectCount);

  // ...
};

# 配置机器

¥Configuring Machines

可以通过将机器选项作为 useMachine(machine, options) 的第二个参数传递来配置现有机器。

¥Existing machines can be configured by passing the machine options as the 2nd argument of useMachine(machine, options).

示例:'fetchData' 服务和 'notifySuccess' 操作都是可配置的:

¥Example: the 'fetchData' service and 'notifySuccess' action are both configurable:

import { createMachine } from 'xstate';
import { fromPromise } from 'xstate/actors';

const fetchMachine = createMachine({
  id: 'fetch',
  initial: 'idle',
  context: {
    data: undefined,
    error: undefined
  },
  states: {
    idle: {
      on: { FETCH: 'loading' }
    },
    loading: {
      invoke: {
        src: 'fetchData',
        onDone: {
          target: 'success',
          actions: assign({
            data: (_, event) => event.data
          })
        },
        onError: {
          target: 'failure',
          actions: assign({
            error: (_, event) => event.data
          })
        }
      }
    },
    success: {
      entry: 'notifySuccess',
      type: 'final'
    },
    failure: {
      on: {
        RETRY: 'loading'
      }
    }
  }
});

const Fetcher = ({ onResolve }) => {
  const [state, send] = useMachine(fetchMachine, {
    actions: {
      notifySuccess: (ctx) => onResolve(ctx.data)
    },
    actors: {
      fetchData: (_, event) =>
        fromPromise(() =>
          fetch(`some/api/${event.query}`).then((res) => res.json())
        )
    }
  });

  switch (state.value) {
    case 'idle':
      return (
        <button onClick={() => send({ type: 'FETCH', query: 'something' })}>
          Search for something
        </button>
      );
    case 'loading':
      return <div>Searching...</div>;
    case 'success':
      return <div>Success! Data: {state.context.data}</div>;
    case 'failure':
      return (
        <>
          <p>{state.context.error.message}</p>
          <button onClick={() => send('RETRY')}>Retry</button>
        </>
      );
    default:
      return null;
  }
};

# 匹配状态

¥Matching States

当使用 hierarchical (opens new window)parallel (opens new window) 机器时,状态值将是对象,而不是字符串。在这种情况下,最好使用 state.matches(...) (opens new window)

¥When using hierarchical (opens new window) and parallel (opens new window) machines, the state values will be objects, not strings. In this case, it is best to use state.matches(...) (opens new window).

我们可以用 if/else if/else 块来做到这一点:

¥We can do this with if/else if/else blocks:

// ...
if (state.matches('idle')) {
  return /* ... */;
} else if (state.matches({ loading: 'user' })) {
  return /* ... */;
} else if (state.matches({ loading: 'friends' })) {
  return /* ... */;
} else {
  return null;
}

我们也可以继续使用 switch,但是我们必须对我们的做法做出调整。通过将 switch 的表达式设置为 true,我们可以在每个 case 中使用 state.matches(...) (opens new window) 作为谓词:

¥We can also continue to use switch, but we must make an adjustment to our approach. By setting the expression of the switch to true, we can use state.matches(...) (opens new window) as a predicate in each case:

switch (true) {
  case state.matches('idle'):
    return /* ... */;
  case state.matches({ loading: 'user' }):
    return /* ... */;
  case state.matches({ loading: 'friends' }):
    return /* ... */;
  default:
    return null;
}

也可以考虑三元语句,特别是在渲染的 JSX 中:

¥A ternary statement can also be considered, especially within rendered JSX:

const Loader = () => {
  const [state, send] = useMachine(/* ... */);

  return (
    <div>
      {state.matches('idle') ? (
        <Loader.Idle />
      ) : state.matches({ loading: 'user' }) ? (
        <Loader.LoadingUser />
      ) : state.matches({ loading: 'friends' }) ? (
        <Loader.LoadingFriends />
      ) : null}
    </div>
  );
};

# 持续和再水化状态

¥Persisted and Rehydrated State

你可以通过 options.state 使用 useMachine(...) 保持并补充状态:

¥You can persist and rehydrate state with useMachine(...) via options.state:

// ...

// Get the persisted state config object from somewhere, e.g. localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState;

const App = () => {
  const [state, send] = useMachine(someMachine, {
    state: persistedState // provide persisted state config object here
  });

  // state will initially be that persisted state, not the machine's initialState

  return (/* ... */)
}

# 服务

¥Services

可以引用 useMachine(machine) 中创建的 service 作为第三个返回值:

¥The service created in useMachine(machine) can be referenced as the third returned value:

//                  vvvvvvv
const [state, send, service] = useMachine(someMachine);

你可以使用 useEffect (opens new window) 订阅该服务的状态更改:

¥You can subscribe to that service's state changes with the useEffect hook (opens new window):

// ...

useEffect(() => {
  const subscription = service.subscribe((state) => {
    // simple state logging
    console.log(state);
  });

  return subscription.unsubscribe;
}, [service]); // note: service should never change

# 资源

¥Resources

React 中的状态机 (opens new window)

¥State Machines in React (opens new window)