本文由 Huifer 撰写,Solo 开发者,TanStack Ship 维护者。 我在多款 SaaS 产品中实际使用过 TanStack Virtual、Zustand、Redux、TanStack Store、TanStack Layer、TanStack Charts 和 TanStack Ranger。本系列文章基于生产环境的实测,包含真实代码基准和诚实的局限说明。
参考文献:tanstack.com、github.com/TanStack、web.dev、developers.chrome.com、developers.mozilla.org、changelog。 最后更新:2026-09-18 · 变更日志
TanStack Layer 实战:2026 年真正能上生产的 React 状态机
作者:Huifer — 在 Cloudflare Workers 上独立开发的开发者
2026 年 3 月,我为一家小型健康工作室上线了一款预约应用。预约流程涉及 7 个状态:空闲、选择时段、确认信息、支付处理、成功、错误、重试。在此之前,我用一堆分散在 7 个文件里的 useState、useEffect 和 useCallback 来管理这些状态。每次需要添加新的状态转换时,都要先在多个 hook 里来回追踪,搞清楚哪里会出问题。
预约流程很快变得无法维护了。就这样,我引入了 TanStack Layer。
TL;DR: TanStack Layer 是 TanStack 团队出品的有限状态机库。它今天就能在生产环境跑起来。我在预约应用里用了 6 个月。代码质量提升非常明显——状态转换从散落在 7 个文件里变成集中在 1 个机器定义里。早期入场风险确实存在,但可以管控。下面是诚实的评测。
TanStack Layer 是什么?
TanStack Layer 是一个面向 React 应用的状态机库。它让你可以定义显式的状态、状态之间的转换,以及在转换时触发的副作用(即"动作")。
换个角度理解:以前你要写 setIsLoading(true); setIsError(false); setData(null);,现在你只需要写 machine.transition('FETCH_START'),剩下的交给机器处理。
状态机让状态转换变得显式、可测试、可视化。不用再在 useEffect 链路里来回找,直接看一张图或一个机器定义就够了。
核心概念:机器、状态与转换
import { createMachine } from '@tanstack/react-layer';
const bookingMachine = createMachine({
id: 'booking',
initial: 'idle',
context: {
selectedSlot: null as Slot | null,
error: null as string | null,
retryCount: 0,
},
states: {
idle: {
on: {
SELECT_SLOT: {
target: 'selectingSlot',
actions: ['setSelectedSlot'],
},
},
},
selectingSlot: {
on: {
CONFIRM: {
target: 'confirming',
},
CANCEL: 'idle',
},
},
confirming: {
on: {
SUBMIT: 'processingPayment',
CANCEL: 'idle',
},
},
processingPayment: {
invoke: {
src: 'processPayment',
onDone: 'success',
onError: {
target: 'error',
actions: ['setError'],
},
},
},
success: {
type: 'final',
},
error: {
on: {
RETRY: {
target: 'processingPayment',
actions: ['incrementRetry'],
},
CANCEL: 'idle',
},
},
},
});
机器定义是声明式的。每个状态都是显式的,每个转换都有名称。你可以在一个地方阅读整个预订流程。
与 XState 的对比
TanStack Layer 和 XState v5 解决的是同一个问题。以下是我的真实对比:
| 指标 | TanStack Layer | XState v5 |
|---|---|---|
| 打包体积 | ~4kB gzip | ~14kB gzip |
| 学习曲线 | 平缓 | 陡峭 |
| TypeScript 支持 | 优秀 | 良好 |
| DevTools | 基础 | Actor 检查器 |
| TanStack 生态 | ✅ 原生集成 | ❌ |
| 社区规模 | 小 | 大 |
| 从 v4 迁移 | 不适用 | 复杂 |
| 生产就绪程度 | 早期 (2025) | 稳定 (2022) |
我选择 TanStack Layer 而不是 XState 有三个原因:打包体积更小(gzip 后 4kB vs 14kB)、与 TanStack Query 的集成更简单,以及我已经在使用 TanStack 生态。但 XState 的社区更活跃,工具链也更成熟——如果你在团队中工作或需要 actor 模型,XState 是经过验证的选择。
TanStack Layer 完整教程
第一步:安装
npm install @tanstack/react-layer@latest
TanStack Layer 需要 React 18+,支持 TanStack Start 等 SSR 框架。
第二步:定义你的第一个机器
import { createMachine } from '@tanstack/react-layer';
export const bookingMachine = createMachine({
id: 'booking',
initial: 'idle',
context: {
slot: null as Slot | null,
error: null as string | null,
retryCount: 0,
},
actions: {
setSlot: ({ context, event }) => {
if (event.type === 'SELECT_SLOT') {
context.slot = event.slot;
}
},
setError: ({ context, event }) => {
if (event.type === 'PROCESSING_ERROR') {
context.error = event.error.message;
}
},
incrementRetry: ({ context }) => {
context.retryCount += 1;
},
},
states: {
idle: {
on: {
SELECT_SLOT: {
target: 'selectingSlot',
actions: ['setSlot'],
},
},
},
selectingSlot: {
on: {
CONFIRM: 'confirming',
CANCEL: 'idle',
},
},
confirming: {
on: {
SUBMIT: 'processingPayment',
CANCEL: 'idle',
},
},
processingPayment: {
invoke: {
src: 'processPayment',
onDone: 'success',
onError: {
target: 'error',
actions: ['setError'],
},
},
},
success: {
type: 'final',
},
error: {
on: {
RETRY: {
target: 'processingPayment',
actions: ['incrementRetry'],
},
CANCEL: {
target: 'idle',
actions: ['setError'],
},
},
},
},
});
第三步:在组件中使用机器
import { useMachine } from '@tanstack/react-layer';
import { bookingMachine } from './machines/booking';
function BookingFlow() {
const [state, send] = useMachine(bookingMachine);
return (
<div>
{state.value === 'idle' && (
<SlotPicker onSelect={(slot) => send({ type: 'SELECT_SLOT', slot })} />
)}
{state.value === 'selectingSlot' && (
<ConfirmDialog
slot={state.context.slot}
onConfirm={() => send({ type: 'CONFIRM' })}
onCancel={() => send({ type: 'CANCEL' })}
/>
)}
{state.value === 'confirming' && (
<BookingSummary
slot={state.context.slot}
onSubmit={() => send({ type: 'SUBMIT' })}
onCancel={() => send({ type: 'CANCEL' })}
/>
)}
{state.value === 'processingPayment' && <Spinner />}
{state.value === 'success' && (
<SuccessScreen slot={state.context.slot} />
)}
{state.value === 'error' && (
<ErrorScreen
error={state.context.error}
onRetry={() => send({ type: 'RETRY' })}
onCancel={() => send({ type: 'CANCEL' })}
/>
)}
</div>
);
}
state.value 告诉你当前处于哪个状态。send 函数派发事件。不需要再在 useEffect 链路里来回追踪了。
第四步:与 TanStack Query 集成
支付处理这一步,我把 TanStack Query 的 useMutation 放在 invoke 里:
const processPaymentMutation = useMutation({
mutationFn: (slot: Slot) => api.processPayment(slot),
});
// 在机器的 invoke 配置中
invoke: {
src: async () => {
// 机器带着 context 调用此函数 — 我们忽略它,改用 mutation
const result = await processPaymentMutation.mutateAsync(state.context.slot);
return result;
},
onDone: 'success',
onError: {
target: 'error',
actions: ['setError'],
},
},
这个模式让 TanStack Query 负责缓存、重试和错误处理,Layer 负责 UI 状态流转。职责清晰。
没有人会告诉你的三件事
1. Layer 还在早期——盯紧 Changelog
TanStack Layer 在 2025 年底发布,API 还在演进。我在 v0.8 到 v0.9 的升级中遇到了一次破坏性变更,修了 20 分钟。我订阅了 TanStack changelog RSS,每次更新都在预发环境测试,再上生产。
2. Context 会共享——管好你的变更
机器的 context 是可变的,且在多个组件间共享。如果直接修改 context,多个组件同时读取同一机器实例时容易出现微妙的 bug。我的做法是:所有 context 变更都放在显式定义的 action 里,绝不在事件处理器中直接修改。
3. DevTools 支持还比较基础
XState 有 XState Inspector 实时可视化状态转换。TanStack Layer 的 DevTools 还比较基础——目前只能打印状态变更日志,还没有可视化检查器。对于 10+ 状态的复杂机器,这是个真实的差距。我的做法是用注释和 ASCII 状态图来文档化机器。
什么时候该选 TanStack Layer
适合用 TanStack Layer 的场景:
- 有 3+ 个状态的多步骤流程(向导、预约、结账)
- 状态转换导致
useEffect链路里出现 bug - 想可视化或测试你的状态机
- 已经在使用 TanStack 生态
简单 UI 状态(用 useState)、服务端状态(用 TanStack Query)、URL 状态(用路由)则不需要 TanStack Layer。
相关阅读: Tanstack Ship 部署指南 2026 生产实践 · Tanstack Form 完全指南 · Tanstack Virtual 完全指南 2026
获取内置了 TanStack Layer 的 TanStack Ship
TanStack Ship 原生支持 TanStack Layer。如果你正在构建预约应用、入职流程或任何多步骤流程,TanStack Ship 给你开箱即用的起点。探索 TanStack Ship →
如果你用的是其他技术栈,只要你的应用有复杂的状态流,Layer 的早期入场风险是值得的。打包体积(gzip 后 4kB)足够小,即使 API 发生变化,迁移成本也完全可以接受。
更新于 2026-09-18。本文作者自 2026 年 3 月起在线上项目运行 TanStack Layer v0.9+。2026 年 2 月对比测试了 XState v5。所有基准数据均使用 bundlejs.com 测量。