React 19 新特性实战指南:use、Actions 表单与 ref-as-prop(基于 claude-skills react-expert 技能)
【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills
本指南以 claude-skills 仓库中 react-expert 技能的核心参考文档 react-19-features.md 为主体,系统讲解 React 19 的四大新 Hooks(use()、useActionState、useFormStatus、useOptimistic)以及ref作为普通 prop 的突破性改变。读完本文,你将能够在 Next.js App Router 与纯客户端 React 应用中,用 Server Actions 搭建带加载态、错误态与乐观更新的表单,并用 Suspense 优雅处理数据获取,同时理解每个特性在当前仓库 Skill 工作流中的落地位置。
一、为什么 React 19 值得专门学习
React 19 是 React 生态的一次范式转移:数据获取、表单状态、DOM ref 这些过去需要大量样板代码的领域,如今被收编为内置 API。在 claude-skills 项目中,react-expert SKILL.md 明确将「React 19 features」列为技能的触发场景之一(triggers字段包含React 19、Server Components、Suspense),并在「When to Use This Skill」中列出「Working with React 19 Server Components」「Implementing forms with React 19 actions」两大核心用例,其 Reference Guide 也把本主题指向references/react-19-features.md。
这套文档的定位不是罗列特性,而是给出可直接复制的 TypeScript 代码模板,覆盖四类高频场景:Suspense 数据获取(use())、表单提交状态(useActionState+useFormStatus)、乐观更新(useOptimistic)、以及组件 API 简化(ref作为 prop)。下面逐一展开。
二、use()Hook:在渲染期间读取 Promise 与 Context
use()是 React 19 中一个位置灵活的新 API——它可以像其它 Hooks 一样在组件顶层调用,也可以(区别于useState/useEffect)在条件语句、循环或提前 return 之后调用。它的两种能力分别是:读取一个 Promise(配合 Suspense 挂起渲染直至其落定),以及读取 Context(作为useContext的替代)。
2.1 在渲染中读取 Promise
use()最典型的用法是读取 Promise 并让组件在 Promise 未 resolve 前挂起,由外层的<Suspense>负责展示 fallback:
import { use, Suspense } from 'react'; // Read promises in render function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) { const comments = use(commentsPromise); return ( <ul> {comments.map(c => <li key={c.id}>{c.text}</li>)} </ul> ); } // Parent creates promise, child reads it function Post({ postId }: { postId: string }) { const commentsPromise = fetchComments(postId); return ( <article> <PostContent id={postId} /> <Suspense fallback={<CommentsSkeleton />}> <Comments commentsPromise={commentsPromise} /> </Suspense> </article> ); }关键要点(对应原文档 react-19-features.md 中的示例):
- Promise 由父组件创建并传入:
fetchComments(postId)在父组件Post中发起,子组件Comments只负责use()它。这样数据请求在渲染真正挂起之前就已启动,避免了串行等待。 - 挂起由外层 Suspense 兜底:
use(promise)使组件挂起时,最近的上层<Suspense>会渲染fallback(这里是CommentsSkeleton)。它与异步 Server Component 的流式渲染模式互补——相关内容参见 server-components.md 中「Streaming with Suspense」一节。 - 不重复请求:由于 Promise 对象本身作为 prop 传递,同一 Promise 实例在重渲染间保持稳定,
use()不会触发重复 fetch。
2.2 条件读取 Context
use(ThemeContext)是useContext的替代写法,其独特价值在于可以放在条件分支内——这在之前用useContext会违反 Hooks 规则:
// Read context conditionally function Theme({ children }: { children: React.ReactNode }) { if (someCondition) { const theme = use(ThemeContext); return <div className={theme}>{children}</div>; } return children; }对于「只有满足条件时才需要读取 Context」的场景(例如仅在生产环境注入主题、仅在启用实验功能时读取配置),这是唯一既简洁又合规的写法。全局状态的基础设施仍可沿用 state-management.md 中 Context +createContext的 Provider 模式,只是读取端在 React 19 中多了一个选择。
三、useActionState:把表单提交状态收进一个 Hook
在 React 19 之前,表单提交需要手动维护isPending、error、success等一堆useState。useActionState用一个 Hook 打包了Action 函数 + 状态 + 待处理标志,常与 Server Actions 搭配('use server')使用。
原文档给出的 Newsletter 订阅示例(react-19-features.md)完整如下:
'use client'; import { useActionState } from 'react'; interface FormState { error?: string; success?: boolean; } async function submitAction(prevState: FormState, formData: FormData): Promise<FormState> { 'use server'; const email = formData.get('email') as string; try { await subscribe(email); return { success: true }; } catch { return { error: 'Failed to subscribe' }; } } function NewsletterForm() { const [state, formAction, isPending] = useActionState(submitAction, {}); return ( <form action={formAction}> <input name="email" type="email" required disabled={isPending} /> <button type="submit" disabled={isPending}> {isPending ? 'Subscribing...' : 'Subscribe'} </button> {state.error && <p className="error">{state.error}</p>} {state.success && <p className="success">Subscribed!</p>} </form> ); }需要吃透的细节:
- 签名:
useActionState(action, initialState)返回[state, formAction, isPending]。action的签名固定为(prevState, formData) => nextState;initialState是首次渲染时state的初始值。 'use server'声明:submitAction内部标注'use server'表示这是一个 Server Action,运行在服务端,可安全执行subscribe(email)这类涉及凭据或数据库的操作。注意此处文件同时有'use client'(组件)与函数内'use server'(Action)——这是 Server Action 从客户端组件中导入时的标准写法。- loading 状态:
isPending在 Action 执行期间为true,用于禁用输入框与按钮并切换文案;表单的 action 属性直接绑定formAction,无需手动onSubmit。 - 错误呈现:通过 Action 返回的
state.error/state.success渲染内联提示,比传统在组件里 try/catch 更干净。
react-expert 技能自身的核心工作流模板(SKILL.md)给出了一个更精简的变体——submitForm(_prev, formData)返回问候消息字符串,直接useActionState(submitForm, '')并把返回的消息渲染成段落。这个模式可以推广到评论提交、登录、注册等任何「提交后需要回显结果」的表单。
四、useFormStatus:让子组件感知父级表单的 pending 状态
useFormStatus解决一个经典难题:提交按钮往往被抽成独立组件,无法直接拿到父级<form>的 pending 状态。它必须在<form>的后代组件中调用,读取最近祖先表单的状态(原文档示例见 react-19-features.md):
'use client'; import { useFormStatus } from 'react-dom'; function SubmitButton() { const { pending, data, method, action } = useFormStatus(); return ( <button type="submit" disabled={pending}> {pending ? 'Submitting...' : 'Submit'} </button> ); } // Must be used inside a <form> function ContactForm() { return ( <form action={submitAction}> <input name="message" /> <SubmitButton /> </form> ); }使用要点:
- 注意导入来源:
useFormStatus从react-dom导出(而不是react),这是它与useActionState、useOptimistic的区别。 - 返回值:
{ pending, data, method, action }——pending表示表单是否正在提交;data是当前正在提交的FormData;method是 HTTP 方法;action是被调用的 Action 函数。常规场景只需pending。 - 职责分工:
useActionState负责「表单整体」的状态与 Action 绑定,useFormStatus负责「子组件」观察表单的提交中状态。二者常搭配出现:外层用useActionState管理error/success结果,内层按钮组件用useFormStatus统一处理禁用与 loading 文案,从而避免把按钮逻辑塞进每个表单。 - 约束:若
SubmitButton被渲染在<form>之外,pending始终为false,不会报错但也不会生效——因此文档中特意标注 "Must be used inside a<form>"。
五、useOptimistic:先渲染结果,再落盘
乐观更新(Optimistic UI)指的是:用户操作后立即用预期结果更新界面,后台请求完成后再用真实数据校准。useOptimistic把这一模式压缩成了两个返回值。
原文档的 Todo 列表示例(react-19-features.md):
'use client'; import { useOptimistic } from 'react'; function TodoList({ todos }: { todos: Todo[] }) { const [optimisticTodos, addOptimisticTodo] = useOptimistic( todos, (state, newTodo: Todo) => [...state, newTodo] ); async function addTodo(formData: FormData) { const text = formData.get('text') as string; // Immediately update UI addOptimisticTodo({ id: 'temp', text, completed: false }); // Then persist await createTodo(text); } return ( <> <ul> {optimisticTodos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> <form action={addTodo}> <input name="text" /> <button>Add</button> </form> </> ); }理解这个 Hook 的三个关键点:
- 签名:
useOptimistic(state, updateFn)返回[optimisticState, addOptimistic]。第一个参数是「真实状态」的基底(这里的todos),第二个参数是合并函数(currentState, optimisticValue) => newState,决定乐观值如何叠加到当前状态上。 - 调用时机:在异步操作发起前调用
addOptimisticTodo(...)立即更新界面(示例里用临时 id'temp'标记),随后await createTodo(text)持久化;当真实状态(todos)最终更新时,乐观值会被真实值替换。 - 与表单 Action 的配合:
addTodo直接作为<form action>使用,配合FormData取值,说明乐观更新与 React 19 表单体系是同一套心智模型——先给用户反馈,再后台落地。
六、ref 作为 prop:不再需要forwardRef
React 19 之前,函数组件要接收ref必须包一层forwardRef。React 19 将ref降格为普通 prop,函数组件直接声明即可(原文档示例见 react-19-features.md):
// React 19: ref is just a prop function Input({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> }) { return <input ref={ref} {...props} />; } // No need for forwardRef anymore function Form() { const inputRef = useRef<HTMLInputElement>(null); return <Input ref={inputRef} placeholder="Enter text" />; }要点:
- 类型:
ref在 props 中的类型为React.Ref<HTMLInputElement>,从 props 解构后直接传给原生<input>的ref。 - 收益:消除了
forwardRef这层包裹,组件树更扁平;对于需要透传 ref 的 UI 库组件,这是显著的心智简化。 - 注意:
ref作为 prop 接收属于 React 19 的新行为;对于仍运行在 React 18 及以下的代码库,保留forwardRef写法仍然必要。这也解释了 react-expert 技能为何专门维护了 migration-class-to-modern.md 这类迁移指南——升级 React 19 时,forwardRef的大量调用点都是可以机械替换的。
七、快速参考:一张表选对 Hook,一张表用对模式
原文档在结尾用两张表做了浓缩总结(react-19-features.md),这里完整继承并补充使用场景说明:
Hook 速查
| Hook | Purpose |
|---|---|
use() | Read promise/context in render |
useActionState() | Form action state + pending |
useFormStatus() | Form pending state (child) |
useOptimistic() | Optimistic UI updates |
use():渲染期内联读取数据源;对应「Suspense 数据获取」与「条件 Context 读取」两种模式。useActionState():表单 Action 的返回状态 + 提交中标志,替代手写三四个useState。useFormStatus():供<form>后代组件读取父表单的 pending 状态,专为抽离提交按钮设计。useOptimistic():先渲染预期结果再持久化,适合点赞、评论、加购物车等即时反馈场景。
模式速查
| Pattern | When |
|---|---|
use(promise) | Suspense data fetching |
use(context) | Conditional context read |
useActionState | Server Actions with state |
值得注意的边界:use(promise)适合「读 Promise」的数据流(尤其与 Suspense 组合),而大规模服务端状态缓存、失效与重取仍建议交给 TanStack Query 这类专职方案(参见 state-management.md);useActionState与 Server Actions 是「带状态的提交」首选,单纯无状态的表单提交直接用<form action={serverAction}>(见 server-components.md)即可。
八、在本技能工作流中的落地实践
react-expert 技能在 SKILL.md 中规定了标准工作流:分析需求 → 选择模式 → 用 TypeScript 实现 →tsc --noEmit校验 → 按需 memo 优化 → 用 React Testing Library 测试。使用上述 React 19 特性时应额外遵循以下约束(来自 SKILL.md 的 Constraints 节):
- MUST DO:全程 TypeScript 严格模式;为异步组件配置错误边界(Error Boundary)兜底,避免
use(promise)拒绝时白屏;Suspense 之外的异步 UI 也要有 loading 占位;对传给 memo 化子组件的回调使用useCallback。 - MUST NOT DO:直接修改 state(乐观更新要经由
addOptimistic的 reducer 式合并函数,而非就地 push);用数组索引作列表key(示例中一律使用id或临时 id);在 JSX 内联创建函数导致多余重渲染。
一个典型组合是:useActionState管理提交结果与 pending,useFormStatus统一提交按钮的 loading 展示,useOptimistic在提交前即时回显,use(promise)负责评论等区块的 Suspense 加载——这四个 Hook 覆盖了现代 React 表单从「读取 → 提交 → 反馈」的完整链路。若要进一步系统化掌握配套模式,可继续阅读本技能下的 server-components.md、hooks-patterns.md 与 performance.md,其中分别覆盖 RSC 数据获取与 Server Actions、自定义 Hook 与 Effect 清理、以及memo/useTransition/lazy等性能手段——它们与本文的 React 19 特性共同构成 react-expert 技能的完整知识底座。
【免费下载链接】claude-skills67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer.项目地址: https://gitcode.com/GitHub_Trending/claud/claude-skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考