@xstate/react

@xstate/react 包 包含使用 XStateReact 的实用程序。

[[toc]]

快速开始

  1. 安装 xstate@xstate/react:
  1. npm i xstate @xstate/react

通过 CDN

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

通过使用全局变量 XStateReact

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

通过使用全局变量 XStateReactFSM

  1. 导入 useMachine hook:
  1. import { useMachine } from '@xstate/react';
  2. import { createMachine } from 'xstate';
  3. const toggleMachine = createMachine({
  4. id: 'toggle',
  5. initial: 'inactive',
  6. states: {
  7. inactive: {
  8. on: { TOGGLE: 'active' }
  9. },
  10. active: {
  11. on: { TOGGLE: 'inactive' }
  12. }
  13. }
  14. });
  15. export const Toggler = () => {
  16. const [state, send] = useMachine(toggleMachine);
  17. return (
  18. <button onClick={() => send('TOGGLE')}>
  19. {state.value === 'inactive'
  20. ? 'Click to activate'
  21. : 'Active! Click to deactivate'}
  22. </button>
  23. );
  24. };

示例

API

useMachine(machine, options?)

一个 React hook,它解释给定的 machine 并启动一个在组件的生命周期内运行的服务。

参数

  • machine - XState machine 或延迟返回 machine 的函数:

    1. // 现在的 machine
    2. const [state, send] = useMachine(machine);
    3. // 延迟创建的 machine
    4. const [state, send] = useMachine(() =>
    5. createMachine({
    6. /* ... */
    7. })
    8. );
  • options (optional) - Interpreter options and/or 以下任何 machine 配置选项: guards, actions, services, delays, immediate, context, state.

返回值 一个元组 [state, send, service]:

  • state - 将 machine 的当前状态表示为 XState State 对象。
  • send - 向正在运行的服务发送事件的函数。
  • service - 创建的服务。

useService(service)

::: warning Deprecated

在下一个主要版本中,useService(service) 将被替换为 useActor(service)。 更喜欢使用 useActor(service) hook 来代替服务,因为服务也是演员。

另外,请记住,只有一个参数(事件对象)可以从 useActor(...) 发送到 send(eventObject)。 迁移到 useActor(...) 时,重构 send(...) 调用以仅使用单个事件对象:

  1. const [state, send] = useActor(service);
  2. -send('CLICK', { x: 0, y: 3 });
  3. +send({ type: 'CLICK', x: 0, y: 3 });

:::

订阅现有 [服务] (https://xstate.js.org/docs/guides/interpretation.html) 的状态更改的 React hook

参数

返回值 a tuple of [state, send]:

  • state - 将服务的当前状态表示为 XState State 对象。
  • send - 向正在运行的服务发送事件的函数。

useActor(actor, getSnapshot?)

订阅现有 actor 发出更改的 React hook

参数

  • actor - 一个类似actor的对象,包含.send(...).subscribe(...)方法。
  • getSnapshot - 一个应该从 actor 返回最新发出的值的函数。
    • 默认尝试获取 actor.state,如果不存在则返回 undefined
  1. const [state, send] = useActor(someSpawnedActor);
  2. // 自定义演员
  3. const [state, send] = useActor(customActor, (actor) => {
  4. // 特定于实现的伪代码示例:
  5. return actor.getLastEmittedValue();
  6. });

useInterpret(machine, options?, observer?)

一个 React hook,它返回从带有 optionsmachine 创建的 service,如果指定的话。 它启动服务并在组件的生命周期内运行它。 这类似于useMachine; 但是,useInterpret 允许自定义的observer 订阅service

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

要在渲染中使用服务中的某个状态,请使用 useSelector(...) hook 来订阅它。

Since 1.3.0

参数

  • machine - XState machine 或延迟返回 machine 的函数。
  • options (optional) - Interpreter options and/or 以下任何 machine 配置选项: guards, actions, services, delays, immediate, context, state.
  • observer (optional) - 监听状态更新的观察者或监听者:
    • an observer (e.g., { next: (state) => {/* ... */} })
    • or a listener (e.g., (state) => {/* ... */})
  1. import { useInterpret } from '@xstate/react';
  2. import { someMachine } from '../path/to/someMachine';
  3. const App = () => {
  4. const service = useInterpret(someMachine);
  5. // ...
  6. };

With options + listener:

  1. // ...
  2. const App = () => {
  3. const service = useInterpret(
  4. someMachine,
  5. {
  6. actions: {
  7. /* ... */
  8. }
  9. },
  10. (state) => {
  11. // 订阅状态更改
  12. console.log(state);
  13. }
  14. );
  15. // ...
  16. };

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

一个 React hook,它从 actor 的快照中返回选定的值,例如服务。 如果选择的值发生变化,这个 hook 只会导致重新渲染,由可选的 compare 函数确定。

Since 1.3.0

参数

  • actor - 包含 .send(...).subscribe(...) 方法的服务或类似actor的对象。
  • selector - 一个函数,它将参与者的“当前状态”(快照)作为参数并返回所需的选定值。
  • compare (optional) - 确定当前选定值是否与先前选定值相同的函数。
  • getSnapshot (optional) - 一个应该从 actor 返回最新发出的值的函数。
    • 默认尝试获取 actor.state,如果不存在则返回 undefined。 将自动从服务中提取状态。
  1. import { useSelector } from '@xstate/react';
  2. // 提示:尽可能通过在外部定义选择器来优化选择器
  3. const selectCount = (state) => state.context.count;
  4. const App = ({ service }) => {
  5. const count = useSelector(service, selectCount);
  6. // ...
  7. };

With compare function:

  1. // ...
  2. const selectUser = (state) => state.context.user;
  3. const compareUser = (prevUser, nextUser) => prevUser.id === nextUser.id;
  4. const App = ({ service }) => {
  5. const user = useSelector(service, selectUser, compareUser);
  6. // ...
  7. };

With useInterpret(...):

  1. import { useInterpret, useSelector } from '@xstate/react';
  2. import { someMachine } from '../path/to/someMachine';
  3. const selectCount = (state) => state.context.count;
  4. const App = ({ service }) => {
  5. const service = useInterpret(someMachine);
  6. const count = useSelector(service, selectCount);
  7. // ...
  8. };

asEffect(action)

确保 actionuseEffect 中作为副作用执行,而不是立即执行。

参数

  • action - 一个 action 函数 (e.g., (context, event) => { alert(context.message) }))

返回值 一个特殊的 action 函数,它封装了原始函数,以便 useMachine 知道在 useEffect 中执行它。

示例

  1. const machine = createMachine({
  2. initial: 'focused',
  3. states: {
  4. focused: {
  5. entry: 'focus'
  6. }
  7. }
  8. });
  9. const Input = () => {
  10. const inputRef = useRef(null);
  11. const [state, send] = useMachine(machine, {
  12. actions: {
  13. focus: asEffect((context, event) => {
  14. inputRef.current && inputRef.current.focus();
  15. })
  16. }
  17. });
  18. return <input ref={inputRef} />;
  19. };

asLayoutEffect(action)

确保 actionuseEffect 中作为副作用执行,而不是立即执行。

参数

  • action - 一个 action 函数 (e.g., (context, event) => { alert(context.message) }))

返回值 一个特殊的 action 函数,它封装了原始函数,以便 useMachine 知道在 useEffect 中执行它。

useMachine(machine) with @xstate/fsm

一个 React hook 从 [@xstate/fsm] 解释给定的有限状态 machine 并启动一个在组件的生命周期内运行的服务。

这个特殊的 useMachine 钩子是从 @xstate/react/fsm 导入的

参数

返回值 一个元组 [state, send, service]:

  • state - 将 machine 的当前状态表示为 @xstate/fsm StateMachine.State 对象。
  • send - 向正在运行的服务发送事件的函数。
  • service - 创建的 @xstate/fsm 服务。

示例

  1. import { useEffect } from 'react';
  2. import { useMachine } from '@xstate/react/fsm';
  3. import { createMachine } from '@xstate/fsm';
  4. const context = {
  5. data: undefined
  6. };
  7. const fetchMachine = createMachine({
  8. id: 'fetch',
  9. initial: 'idle',
  10. context,
  11. states: {
  12. idle: {
  13. on: { FETCH: 'loading' }
  14. },
  15. loading: {
  16. entry: ['load'],
  17. on: {
  18. RESOLVE: {
  19. target: 'success',
  20. actions: assign({
  21. data: (context, event) => event.data
  22. })
  23. }
  24. }
  25. },
  26. success: {}
  27. }
  28. });
  29. const Fetcher = ({
  30. onFetch = () => new Promise((res) => res('some data'))
  31. }) => {
  32. const [state, send] = useMachine(fetchMachine, {
  33. actions: {
  34. load: () => {
  35. onFetch().then((res) => {
  36. send({ type: 'RESOLVE', data: res });
  37. });
  38. }
  39. }
  40. });
  41. switch (state.value) {
  42. case 'idle':
  43. return <button onClick={(_) => send('FETCH')}>Fetch</button>;
  44. case 'loading':
  45. return <div>Loading...</div>;
  46. case 'success':
  47. return (
  48. <div>
  49. Success! Data: <div data-testid="data">{state.context.data}</div>
  50. </div>
  51. );
  52. default:
  53. return null;
  54. }
  55. };

配置 Machines

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

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

  1. const fetchMachine = createMachine({
  2. id: 'fetch',
  3. initial: 'idle',
  4. context: {
  5. data: undefined,
  6. error: undefined
  7. },
  8. states: {
  9. idle: {
  10. on: { FETCH: 'loading' }
  11. },
  12. loading: {
  13. invoke: {
  14. src: 'fetchData',
  15. onDone: {
  16. target: 'success',
  17. actions: assign({
  18. data: (_, event) => event.data
  19. })
  20. },
  21. onError: {
  22. target: 'failure',
  23. actions: assign({
  24. error: (_, event) => event.data
  25. })
  26. }
  27. }
  28. },
  29. success: {
  30. entry: 'notifySuccess',
  31. type: 'final'
  32. },
  33. failure: {
  34. on: {
  35. RETRY: 'loading'
  36. }
  37. }
  38. }
  39. });
  40. const Fetcher = ({ onResolve }) => {
  41. const [state, send] = useMachine(fetchMachine, {
  42. actions: {
  43. notifySuccess: (ctx) => onResolve(ctx.data)
  44. },
  45. services: {
  46. fetchData: (_, e) =>
  47. fetch(`some/api/${e.query}`).then((res) => res.json())
  48. }
  49. });
  50. switch (state.value) {
  51. case 'idle':
  52. return (
  53. <button onClick={() => send('FETCH', { query: 'something' })}>
  54. Search for something
  55. </button>
  56. );
  57. case 'loading':
  58. return <div>Searching...</div>;
  59. case 'success':
  60. return <div>Success! Data: {state.context.data}</div>;
  61. case 'failure':
  62. return (
  63. <>
  64. <p>{state.context.error.message}</p>
  65. <button onClick={() => send('RETRY')}>Retry</button>
  66. </>
  67. );
  68. default:
  69. return null;
  70. }
  71. };

Matching 状态

使用 hierarchicalparallel machine 时, 状态值将是对象,而不是字符串。 在这种情况下,最好使用 state.matches(...)

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

  1. // ...
  2. if (state.matches('idle')) {
  3. return /* ... */;
  4. } else if (state.matches({ loading: 'user' })) {
  5. return /* ... */;
  6. } else if (state.matches({ loading: 'friends' })) {
  7. return /* ... */;
  8. } else {
  9. return null;
  10. }

我们也可以继续使用switch,但我们必须对我们的方法进行调整。 通过将switch的表达式设置为true,我们可以使用[state.matches(...)](https://xstate.js.org/docs/guides/states.html#state- 方法和获取machine)作为每个case中的谓词:

  1. switch (true) {
  2. case state.matches('idle'):
  3. return /* ... */;
  4. case state.matches({ loading: 'user' }):
  5. return /* ... */;
  6. case state.matches({ loading: 'friends' }):
  7. return /* ... */;
  8. default:
  9. return null;
  10. }

也可以考虑使用三元语句,尤其是在呈现的 JSX 中:

  1. const Loader = () => {
  2. const [state, send] = useMachine(/* ... */);
  3. return (
  4. <div>
  5. {state.matches('idle') ? (
  6. <Loader.Idle />
  7. ) : state.matches({ loading: 'user' }) ? (
  8. <Loader.LoadingUser />
  9. ) : state.matches({ loading: 'friends' }) ? (
  10. <Loader.LoadingFriends />
  11. ) : null}
  12. </div>
  13. );
  14. };

持续和再融合状态

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

  1. // ...
  2. // 从某处获取持久状态配置对象,例如 localStorage
  3. const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key')) || someMachine.initialState;
  4. const App = () => {
  5. const [state, send] = useMachine(someMachine, {
  6. state: persistedState // 在此处提供持久状态配置对象
  7. });
  8. // 状态最初将是持久状态,而不是 machine 的初始状态
  9. return (/* ... */)
  10. }

服务

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

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

你可以使用 useEffect hook 订阅该服务的状态更改:

  1. // ...
  2. useEffect(() => {
  3. const subscription = service.subscribe((state) => {
  4. // 简单的状态 log
  5. console.log(state);
  6. });
  7. return subscription.unsubscribe;
  8. }, [service]); // 注意:服务不应该改变

从 0.x 迁移

  • 对于使用 invokespawn(...) 创建的衍生演员,请使用 useActor() hook 而不是 useService()

    1. -import { useService } from '@xstate/react';
    2. +import { useActor } from '@xstate/react';
    3. -const [state, send] = useService(someActor);
    4. +const [state, send] = useActor(someActor);

Resources

State Machines in React