TanStack Layer 实战:2026 年真正能上生产的 React 状态机

TanStack Layer 是 TanStack 团队推出的 React 有限状态机库。作者在一个预约应用里跑了 6 个月,告诉你哪些地方好用、哪些地方要踩坑,以及现在是否值得入场。

Huifer
Huifer
September 18, 20264 min read
其他语言:Deutsch · English

本文由 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 链路里来回找,直接看一张图或一个机器定义就够了。

核心概念:机器、状态与转换

tsx
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 LayerXState 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 完整教程

第一步:安装

bash
npm install @tanstack/react-layer@latest

TanStack Layer 需要 React 18+,支持 TanStack Start 等 SSR 框架。

第二步:定义你的第一个机器

tsx
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'],
        },
      },
    },
  },
});

第三步:在组件中使用机器

tsx
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 里:

tsx
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 测量。