@xstate/react
@xstate/react 包 包含用于在 React 中使用 XState 的钩子和辅助函数。
模板
使用以下模板可以快速开始使用 XState 和 React:
安装
安装最新版本的 xstate
和 @xstate/react
。xstate
是 @xstate/react
的一个对等依赖。
- npm
- pnpm
- yarn
npm install xstate @xstate/react
pnpm install xstate @xstate/react
yarn add 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。
参数
machine
- 一个 XState machineoptions?
(可选) - 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 refselector
- 一个函数,它将 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 引用的辅助方法。
参数
logic
- Actor 逻辑,例如 XState machine
返回一个包含以下属性的 React Context 对象:
Provider
- 一个 React Context Provider 组件,具有以下属性:logic
- Actor 逻辑,例如 XState machine,必须与传递给createActorContext(...)
的 actor 逻辑类型相同
useSelector(selector, compare?)
- 一个 React hook,接受一个selector
函数和可选的compare
函数,并返回从 actor 快照中选定的值useActorRef()
- 一个 React hook,返回从 actorlogic
创建的 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 不应更改
资源
即将推出