Skip to content

@xstate/react

@xstate/react 包 包含用于在 React 中使用 XState 的钩子和辅助函数。

模板

使用以下模板可以快速开始使用 XState 和 React:

安装

安装最新版本的 xstate@xstate/reactxstate@xstate/react 的一个对等依赖。

npm install xstate @xstate/react

示例

即将推出

API

useActor(actorLogic, options?)

一个 React hook,它从给定的 actorLogic 创建一个 actor,并启动一个在组件生命周期内运行的 actor。

参数

  • actorLogic - 用于创建 actor 的逻辑;例如 createMachine(...)fromPromise(...) 等。
  • options? (可选) - Actor 选项。

返回 一个 [snapshot, send, actorRef] 元组:

  • snapshot - 表示 actor 的当前状态。
  • send - 一个向运行中的 actor 发送事件的函数。
  • actorRef - 启动的 actor。

示例

import { fromPromise } from 'xstate';
import { useActor } from '@xstate/react';

const promiseLogic = fromPromise(async () => {
const data = await getData(/* ... */);

return data;
});

function Component() {
const [state, send] = useActor(promiseLogic);

if (state.status === 'done') {
return <div>{state.output}</div>;
}

if (state.status === 'active') {
return <div>Loading...</div>;
}

return null;
}

useMachine(machine, options?)

一个 React hook,它从给定的 machine 创建一个 actor,并启动一个在组件生命周期内运行的 actor。

参数

返回 一个 [snapshot, send, actorRef] 元组:

  • snapshot - 表示机器的当前状态。
  • send - 一个向运行中的 actor 发送事件的函数。
  • actorRef - 启动的 actor。

示例

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

function Component() {
const [snapshot, send] = useMachine(machine);

// 提供了实现的机器
// 将保持提供的实现是最新的
const [snapshot, send] = useMachine(
machine.provide({
actions: {
doSomething: ({ context }) => {
// ...
},
},
}),
);
}

useActorRef(machine, options?)

一个 React hook,它返回从 machine 创建的 actorRef,并带有传递给 createActor(logic, options) 的 actor options(如果指定)。它启动 actor ref 并在组件的生命周期内运行它。

useActorRef(...) hook 在你需要细粒度控制时非常有用,例如添加日志记录或最小化重新渲染。与 useActor(...) 不同,后者会将机器的每次更新刷新到 React 组件,useActorRef(...) 则返回一个静态引用(仅指向机器 actor),其状态变化时不会重新渲染。

你可以使用 useSelector(...) hook 从 actorRef 的快照中选择部分数据,每当它更新时。

参数

  • actorLogic - 用于创建 actor 的逻辑;例如 createMachine(...)fromPromise(...) 等。
  • options? (可选) - Actor 选项。
import { useActorRef } from '@xstate/react';
import { someMachine } from '../path/to/someMachine';

const App = () => {
const actorRef = useActorRef(someMachine);

// ...
};

提供机器实现:

// ...

const App = () => {
const actorRef = useActorRef(
someMachine.provide({
actions: {
// ...
},
}),
);

// ...
};

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

一个 React hook,它从 actorRef 的快照中返回选定的值,例如 actor ref。只有当选定的值改变时,此 hook 才会导致重新渲染,这由可选的 compare 函数决定。

参数

  • actorRef - 一个 actor ref
  • selector - 一个函数,它将 actor 的快照作为参数并返回所需的选定值。
  • compare (可选) - 一个函数,用于确定当前选定的值是否与之前的选定值相同。
  • getSnapshot (可选) - 一个函数,应该返回 actor 最新发出的值。
    • 默认情况下尝试获取 actor.state,如果不存在则返回 undefined。将自动从 actor refs 中提取状态。

示例

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

// 提示:尽可能在外部定义选择器以优化选择器
const selectCount = (snapshot) => snapshot.context.count;

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

// ...
};

使用 compare 函数:

// ...

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

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

// ...
};

createActorContext(logic)

返回一个 React Context 对象,它从提供的 actor logic 创建一个 actor,并通过 React Context 使该 actor 可用。它包含用于访问状态和 actor 引用的辅助方法。

参数

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

  • Provider - 一个 React Context Provider 组件,具有以下属性:
    • logic - Actor 逻辑,例如 XState machine,必须与传递给 createActorContext(...) 的 actor 逻辑类型相同
  • useSelector(selector, compare?) - 一个 React hook,接受一个 selector 函数和可选的 compare 函数,并返回从 actor 快照中选定的值
  • useActorRef() - 一个 React hook,返回从 actor logic 创建的 actor 引用

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

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

const SomeMachineContext = createActorContext(someMachine);

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

在组件中使用 actor:

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

function SomeComponent() {
const count = SomeMachineContext.useSelector((state) => state.context.count);
const someActorRef = SomeMachineContext.useActorRef();

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

提供类似的机器:

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

function SomeComponent() {
return (
<SomeMachineContext.Provider
logic={someMachine.provide({
actions: {
someAction: differentImplementation,
},
// ... 更多实现
})}
>
<SomeOtherComponent />
</SomeMachineContext.Provider>
);
}

浅比较

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

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

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

// ...

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

const App = ({ actorRef }) => {
// shallowEqual 比较器用于比较对象,即使对象的引用可能会改变,但浅层对象值是相等的
const user = useSelector(actorRef, selectUser, shallowEqual);

// ...
};

使用 useActorRef(...)

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

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

const App = () => {
const actorRef = useActorRef(someMachine);
const count = useSelector(actorRef, selectCount);

// ...
};

配置状态机

可以通过在 machine.provide(implementations) 中提供不同的实现来定制现有的状态机。

示例:'fetchData' actor 引用和 'notifySuccess' 动作都是可配置的:

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.output,
}),
},
onError: {
target: 'failure',
actions: assign({
error: ({ event }) => event.error,
}),
},
},
},
success: {
entry: 'notifySuccess',
type: 'final',
},
failure: {
on: {
RETRY: 'loading',
},
},
},
});

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

switch (state.value) {
case 'idle':
return (
<button onClick={() => send({ type: 'FETCH', query: 'something' })}>
搜索某物
</button>
);
case 'loading':
return <div>搜索中...</div>;
case 'success':
return <div>成功!数据:{state.context.data}</div>;
case 'failure':
return (
<>
<p>{state.context.error.message}</p>
<button onClick={() => send({ type: 'RETRY' })}>重试</button>
</>
);
default:
return null;
}
};

输入

你可以向 actors 提供输入

匹配状态

当使用分层并行状态机时,状态值将是对象而不是字符串。在这种情况下,最好使用state.matches(...)

我们可以使用 if/else if/else 语句来实现这一点:

// ...
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(...) 作为谓词:

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

也可以考虑在渲染的 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>
);
};

持久化和恢复状态

你可以通过在 useMachine(...) 的第二个参数中使用 options.snapshot 来持久化和恢复状态:

// ...

// 从某处获取持久化的状态配置对象,例如 localStorage
const persistedState = JSON.parse(localStorage.getItem('some-persisted-state-key'));

const App = () => {
const [state, send] = useMachine(someMachine, {
snapshot: persistedState // 在此处提供持久化的状态配置对象
});

// state 将最初是持久化的状态,而不是机器的 initialState

return (/* ... */)
}

Actor 引用

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

//                  vvvvvvvv
const [state, send, actorRef] = useMachine(someMachine);

你可以使用 useEffect hook 订阅该 actor ref 的状态变化:

// ...

useEffect(() => {
const subscription = actorRef.subscribe((snapshot) => {
// 简单日志记录
console.log(snapshot);
});

return subscription.unsubscribe;
}, [actorRef]); // 注意:actor ref 不应更改

资源

即将推出