# @xstate/react
这些 XState v4 文档不再维护
XState v5 现已推出!阅读有关 XState v5 的更多信息 (opens new window) 和 查看 XState v5 文档 (opens new window)。
¥XState v5 is out now! Read more about XState v5 (opens new window) and check out the XState v5 docs (opens new window).
@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
安装
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
导入
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) 和/或以下任何机器配置选项:guards
、actions
、services
、delays
、immediate
、context
、state
。如果计算机已包含任何这些选项,它们将被合并,并且这些选项优先。¥
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
- 将机器的当前状态表示为 XStateState
对象。¥
state
- Represents the current state of the machine as an XStateState
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 theactor
.默认尝试从
actor.getSnapshot()
获取快照,如果不存在则返回undefined
。¥Defaults to attempting to get the snapshot from
actor.getSnapshot()
, or returningundefined
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 钩子,返回从 machine
和 options
创建的 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) 和/或以下任何机器配置选项:guards
、actions
、services
、delays
、immediate
、context
、state
。如果计算机已包含任何这些选项,它们将被合并,并且这些选项优先。¥
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 theactor
.默认尝试从
actor.getSnapshot()
获取快照,如果不存在则返回undefined
。会自动从服务中提取状态。¥Defaults to attempting to get the snapshot from
actor.getSnapshot()
, or returningundefined
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
machine
- XState 机器 (opens new window) 或延迟返回机器的函数。¥
machine
- An XState machine (opens new window) or a function that lazily returns a machine.
返回
¥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:machine
- XState 机器 (opens new window) 必须与传递给createActorContext(...)
的机器类型相同¥
machine
- An XState machine (opens new window) that must be of the same type as the machine passed tocreateActorContext(...)
useActor()
- 一个从 React Context 返回[state, send]
元组的 React hook¥
useActor()
- a React hook that returns a tuple of[state, send]
from the React ContextuseSelector(selector, compare?)
- 一个 React hook,它接受selector
函数和可选的compare
函数并返回从 actor 快照中选择的值¥
useSelector(selector, compare?)
- a React hook that takes in aselector
function and optionalcompare
function and returns the selected value from the actor snapshotuseActorRef()
- 一个 React hook,返回解释后的machine
的 actor ref¥
useActorRef()
- a React hook that returns the actor ref of the interpretedmachine
为参与者创建一个 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