---
title: "用 Vite 搭 React 19 任务管理器：Actions 与乐观更新入门 - Angryecho"
description: "想快速摸到 React 19 的 Actions、useActionState、useOptimistic，不必一上来就上 Next.js。"
canonical_url: "https://www.angryecho.com/c/React/p/P2TBBo2O0HnMUUz0o6xLr"
meta:
  "og:description": "想快速摸到 React 19 的 Actions、useActionState、useOptimistic，不必一上来就上 Next.js。"
  "og:image": "https://www.angryecho.com/_og/d/c_Post%2Ctitle_~5ZCQ5qe9572R%2Cdescription_~5ZCQ5qe95LiA5LiL77yM5bCx5LiK5ZCQ5qe9572R44CC%2CpostId_P2TBBo2O0HnMUUz0o6xLr%2Crevision_~MjAyNi0xMC0yOFQwMjo1NjowMC4yNzc5NDRa%2Cp_Ii9jL1JlYWN0L3AvUDJUQkJvMk8wSG5NVVV6MG82eExyIg%2Cs_7SBJlWZNChjQWqqN.png"
  "og:image:height": "630"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "用 Vite 搭 React 19 任务管理器：Actions 与乐观更新入门"
---

> Index of every page on this site: [llms.txt](https://www.angryecho.com/llms.txt)

# **用 Vite 搭 React 19 任务管理器：Actions 与乐观更新入门**

想快速摸到 React 19 的 Actions、`useActionState`、`useOptimistic`，不必一上来就上 Next.js。技术栈博文以 React 19.2 + TypeScript + Vite 为基线，带出一个可独立运行的 TaskFlow 任务管理示例：创建、筛选、标记完成、删除，数据暂存 `localStorage`，之后可把数据层换成 REST。\[1]

## 准备工作

- Node.js：Vite 文档要求约 `20.19+` 或 `22.12+`，建议用仍在维护的 LTS（原文举例 Node.js 24 LTS）。
- 包管理器：npm / pnpm / Yarn / Bun 任选。
- 编辑器与可选扩展：VS Code 等；浏览器装 React Developer Tools。

先确认环境：

```bash
node -v
npm -v
```

## 操作步骤

### 1、用 Vite 创建 React + TS 项目

```bash
npm create vite@latest taskflow -- --template react-ts
cd taskflow
npm install
npm install react@latest react-dom@latest
npm install -D @types/react@latest @types/react-dom@latest
npm run dev
```

浏览器打开 `http://localhost:5173`，能看到默认页面即成功。可用 `npm ls react react-dom` 确认已到 19.x。为何这样：官方已弃用 Create React App；Vite 模板适合纯客户端练手，显式安装最新 React 才能稳定用到 Actions 相关 API。\[1]

### 2、拆领域模型与数据层

在 `src/domain/task.ts` 定义任务类型（`id`、`title`、`completed`、`createdAt`）。在 `src/services/taskApi.ts` 用 `localStorage` 封装 `list` / `create` / `toggle` / `remove`：读写前把 `JSON.parse` 结果当 `unknown` 做运行时校验，写一点延迟模拟异步。不要在组件里直接碰存储。

为什么：组件只关心「任务列表变了」；以后换 HTTP 只改 `taskApi`。确认方式：在控制台调用 `taskApi.create` 后刷新，数据仍在。\[1]

### 3、用表单 Action 做「创建任务」

`TaskComposer` 里用 `useActionState` 接管提交：

```tsx
const [state, submitAction] = useActionState(async (_prev, formData) => {
  const title = String(formData.get('title') ?? '').trim();
  if (title.length < 2) return { status: 'error', message: '至少两个字符' };
  const task = await taskApi.create(title);
  onCreated(task);
  return { status: 'success', message: '创建成功' };
}, { status: 'idle', message: '' });
```

表单写 `<form action={submitAction}>`。提交按钮拆成表单**内部**子组件，调用 `useFormStatus()` 读 `pending`，禁用按钮并显示「创建中」。

做完怎么确认：输入单字符应出错误提示；合法标题提交后列表出现新任务，输入框清空，按钮短暂 pending。\[1]

### 4、列表操作用乐观更新

在 `App` 用 `useOptimistic(tasks, reducer)`，勾选完成 / 删除时包进 `startTransition`：先 `applyOptimisticAction`，再 `await taskApi.toggle/remove`，成功则同步基础状态，失败则设错误文案（乐观层会回到基础状态）。适合勾选、删除普通项；支付、余额不要盲目乐观。\[1]

### 5、筛选、延迟搜索与自测清单

用 `useState` 管「全部 / 待完成 / 已完成」，搜索词经 `useDeferredValue` 再过滤，减轻输入卡顿。跑 `npm run dev` 按序自测：创建三条 → 筛选 → 搜索 → 勾选 → 删除 → 刷新仍在。可选再加 Vitest + Testing Library 测表单校验（原文有完整示例）。\[1]

## 常见问题

- 开发环境 Effect 跑两次：多半是 `StrictMode` 检查，生产不会双挂；保证 cleanup 可安全重复执行即可。\[1]
- `fetch` 404 进不了 `catch`：HTTP 错误不会自动 reject，必须先判断 `response.ok`。\[1]
- 列表输入错位：检查是否用了数组下标当 `key`，改成稳定业务 id。\[1]

## 注意事项

- `useFormStatus` 必须在目标 `<form>` 的子组件里调用。
- `useOptimistic` 更新要落在 Action / `startTransition` 内。
- 纯客户端没有 RSC；需要 SEO / Server Components 再考虑 Next.js 等。前端环境变量不要放密钥。\[1]

> 说明：步骤据技术栈原文（更新于 2026 年 8 月）整理，以本机 React / Vite 版本为准。

## 来源

> \[1] [React 19 从入门到工程实践：核心原理、新特性与项目实战](https://jishuzhan.net/article/2092430112126586882)，技术栈，2026年8月26日。

#React19 #Vite #TypeScript #useActionState #useOptimistic #前端教程

排序方式 :

### **成为第一条评论！**

Nobody's responded to this post yet. Add your thoughts and get the conversation going.

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://www.angryecho.com/c/React/p/P2TBBo2O0HnMUUz0o6xLr#post","@type":"DiscussionForumPosting","commentCount":0,"dateModified":"2026-10-28T02:56:00.277Z","datePublished":"2026-08-26T01:53:00.000Z","description":"想快速摸到 React 19 的 Actions、useActionState、useOptimistic，不必一上来就上 Next.js。","headline":"用 Vite 搭 React 19 任务管理器：Actions 与乐观更新入门","isPartOf":"https://www.angryecho.com/c/React","mainEntityOfPage":"https://www.angryecho.com/c/React/p/P2TBBo2O0HnMUUz0o6xLr","text":"想快速摸到 React 19 的 Actions、useActionState、useOptimistic，不必一上来就上 Next.js。技术栈博文以 React 19.2 + TypeScript + Vite 为基线，带出一个可独立运行的 TaskFlow 任务管理示例：创建、筛选、标记完成、删除，数据暂存 localStorage，之后可把数据层换成 REST。[1]\n\n准备工作\n\n\n\n\n\nNode.js：Vite 文档要求约 20.19+ 或 22.12+，建议用仍在维护的 LTS（原文举例 Node.js 24 LTS）。\n\n\n\n包管理器：npm / pnpm / Yarn / Bun 任选。\n\n\n\n编辑器与可选扩展：VS Code 等；浏览器装 React Developer Tools。\n\n先确认环境：\n\nnode -v\nnpm -v\n\n操作步骤\n\n1、用 Vite 创建 React + TS 项目\n\nnpm create vite@latest taskflow -- --template react-ts\ncd taskflow\nnpm install\nnpm install react@latest react-dom@latest\nnpm install -D @types/react@latest @types/react-dom@latest\nnpm run dev\n\n浏览器打开 http://localhost:5173，能看到默认页面即成功。可用 npm ls react react-dom 确认已到 19.x。为何这样：官方已弃用 Create React App；Vite 模板适合纯客户端练手，显式安装最新 React 才能稳定用到 Actions 相关 API。[1]\n\n2、拆领域模型与数据层\n\n在 src/domain/task.ts 定义任务类型（id、title、completed、createdAt）。在 src/services/taskApi.ts 用 localStorage 封装 list / create / toggle / remove：读写前把 JSON.parse 结果当 unknown 做运行时校验，写一点延迟模拟异步。不要在组件里直接碰存储。\n\n为什么：组件只关心「任务列表变了」；以后换 HTTP 只改 taskApi。确认方式：在控制台调用 taskApi.create 后刷新，数据仍在。[1]\n\n3、用表单 Action 做「创建任务」\n\nTaskComposer 里用 useActionState 接管提交：\n\nconst [state, submitAction] = useActionState(async (_prev, formData) => {\n  const title = String(formData.get('title') ?? '').trim();\n  if (title.length \u003C 2) return { status: 'error', message: '至少两个字符' };\n  const task = await taskApi.create(title);\n  onCreated(task);\n  return { status: 'success', message: '创建成功' };\n}, { status: 'idle', message: '' });\n\n表单写 \u003Cform action={submitAction}>。提交按钮拆成表单内部子组件，调用 useFormStatus() 读 pending，禁用按钮并显示「创建中」。\n\n做完怎么确认：输入单字符应出错误提示；合法标题提交后列表出现新任务，输入框清空，按钮短暂 pending。[1]\n\n4、列表操作用乐观更新\n\n在 App 用 useOptimistic(tasks, reducer)，勾选完成 / 删除时包进 startTransition：先 applyOptimisticAction，再 await taskApi.toggle/remove，成功则同步基础状态，失败则设错误文案（乐观层会回到基础状态）。适合勾选、删除普通项；支付、余额不要盲目乐观。[1]\n\n5、筛选、延迟搜索与自测清单\n\n用 useState 管「全部 / 待完成 / 已完成」，搜索词经 useDeferredValue 再过滤，减轻输入卡顿。跑 npm run dev 按序自测：创建三条 → 筛选 → 搜索 → 勾选 → 删除 → 刷新仍在。可选再加 Vitest + Testing Library 测表单校验（原文有完整示例）。[1]\n\n常见问题\n\n\n\n\n\n开发环境 Effect 跑两次：多半是 StrictMode 检查，生产不会双挂；保证 cleanup 可安全重复执行即可。[1]\n\n\n\nfetch 404 进不了 catch：HTTP 错误不会自动 reject，必须先判断 response.ok。[1]\n\n\n\n列表输入错位：检查是否用了数组下标当 key，改成稳定业务 id。[1]\n\n注意事项\n\n\n\n\n\nuseFormStatus 必须在目标 \u003Cform> 的子组件里调用。\n\n\n\nuseOptimistic 更新要落在 Action / startTransition 内。\n\n\n\n纯客户端没有 RSC；需要 SEO / Server Components 再考虑 Next.js 等。前端环境变量不要放密钥。[1]\n\n\n\n说明：步骤据技术栈原文（更新于 2026 年 8 月）整理，以本机 React / Vite 版本为准。\n\n来源\n\n\n\n[1] React 19 从入门到工程实践：核心原理、新特性与项目实战，技术栈，2026年8月26日。\n\n#React19 #Vite #TypeScript #useActionState #useOptimistic #前端教程","url":"https://www.angryecho.com/c/React/p/P2TBBo2O0HnMUUz0o6xLr","author":{"@type":"Person","name":"用户032484","url":"https://www.angryecho.com/u/ntyUOPFv"},"interactionStatistic":[{"@type":"InteractionCounter","interactionType":"https://schema.org/LikeAction","userInteractionCount":0},{"@type":"InteractionCounter","interactionType":"https://schema.org/DislikeAction","userInteractionCount":0},{"@type":"InteractionCounter","interactionType":"https://schema.org/ViewAction","userInteractionCount":0},{"@type":"InteractionCounter","interactionType":"https://schema.org/ShareAction","userInteractionCount":0}]}]}
```

## Sitemap

See the full [sitemap](https://www.angryecho.com/sitemap.md) for all pages.
