你还在用useState保存表单数据?来看React 19是如何做的!【useActionState实战详解】

本公众号会持续更新技术干货,感谢大家支持,欢迎点赞、评论、留言。

之前介绍了React19的useOptimistic的用法和具体业务场景的实战,今天讲useActionState,useFormStatus。

我会整理出一些react19新的特性、新的api给大家详细分析以及其实战用法,不足之处还望各位大佬指出,一起探讨进步,感谢大家的支持~

1 为什么需要这两个Hook

在React 18及之前版本,管理表单需手动处理:

  • 状态变量爆炸:需定义isSubmitting、error、data等多个useState

  • 逻辑分散:提交逻辑、状态更新、错误处理分散在useEffect和事件回调中

  • 组件耦合:子组件需通过props传递状态(如禁用按钮需父组件传isSubmitting)

React 19的解决方案:

  • useActionState:统一管理表单提交状态(包括pending状态、返回值、错误信息)

  • useFormStatus:在表单子组件内直接访问提交状态(无需props透传)

2 核心概念与API解析

先来看这两个hooks的用法:

2.1 useActionState

arduino 复制代码
const [state, formAction, isPending] = useActionState(
  actionFn,  // 表单提交函数
  initialState // 初始状态
);
  • 参数:
    • ctionFn(prevState, formData):接收上一次状态和表单数据,返回新状态
    • initialState:状态初始值(可序列化数据)
  • 返回值:
    • state:当前状态(初始为initialState,提交后更新为actionFn返回值)
    • formAction:绑定到 <form action/>或<button formAction/>的函数
    • isPending:提交状态(true表示进行中)

2.2 useFormStatus

kotlin 复制代码
const { pending, data } = useFormStatus();
  • 返回值:
    • pending:父级表单是否提交中(等价于useActionState的isPending)
    • data:当前提交的表单数据(FormData类型)
  • 关键限制:
    • 必须作为<form>的直接子组件使用,否则无法获取状态

3 实战案例:用户评论表单

业务场景:

  1. 提交时显示Loading状态,禁用提交按钮
  2. 提交后显示成功/错误提示
  3. 在输入框组件中实时显示正在提交的内容

先来看react19(以下用nextJs为例)中的具体实现(使用新API):

先定义一个server-action.js文件,里面定义一个后台API的请求,这里用saveToDatabase(comment) mock了数据库操作,当然也可以是一个正常的后台API。

javascript 复制代码
// 服务端Action (server-action.js)
'use server';
export async function submitComment(prevState, formData) {
  const comment = formData.get('comment');
  if (!comment.trim()) {
    return { error: '评论内容不能为空!' };
  }
  try {
    await saveToDatabase(comment); // 模拟数据库保存
    return { message: '评论发布成功!' };
  } catch (err) {
    return { error: '提交失败,请重试' };
  }
}

紧接着我们定义两个子组件,SubmitButton.jsx和CommentInput.jsx

javascript 复制代码
import { useActionState } from 'react';
import { useFormStatus } from 'react-dom';
import { submitComment } from './server-action';

// 子组件:输入框(使用useFormStatus)
function CommentInput() {
  const { pending, data } = useFormStatus();
  return (
    <div>
      <label>您的评论:</label>
      <input 
        name="comment" 
        disabled={pending}
        placeholder={pending ? `正在提交: ${data?.get('comment')}` : ''}
      />
    </div>
  );
}

// 子组件:提交按钮(使用useFormStatus)
function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '发表评论'}
    </button>
  );
}

然后是父组件CommentForm,要记住!useActionState这个方法是只能在client端使用的,所以在nextJs中记得加上'use client'在头部

javascript 复制代码
'use client'

// 父组件:表单(使用useActionState)
export default function CommentForm() {
  const [state, formAction, isPending] = useActionState(
    submitComment, 
    { message: null, error: null }
  );

  return (
    <form action={formAction}>
      <CommentInput />  {/* 子组件1:输入框 */}
      <SubmitButton />   {/* 子组件2:按钮 */}
      
      {/* 显示结果反馈 */}
      {state.error && <p className="error">{state.error}</p>}
      {state.message && <p className="success">{state.message}</p>}
    </form>
  );
}

代码解析:

  1. 状态自动传递
    • useActionState管理全局状态(state、isPending)
    • useFormStatus让深层子组件直接读取状态(无需props透传,直接👏🏻)
  2. 精准UI反馈
    • 输入框组件CommentInput独立显示当前提交内容(通过data.get('comment'))
    • 按钮自动禁用且文本变化(通过pending状态)
  3. 错误处理内聚
    • 所有错误逻辑封装在submitComment中,返回结构化的state

4 与React 18实现的对比

以下为实现相同功能的React 18代码:

javascript 复制代码
// React 18实现(手动状态管理)
function CommentForm() {
  const [comment, setComment] = useState('');
  const [isPending, setIsPending] = useState(false);
  const [error, setError] = useState(null);
  const [message, setMessage] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsPending(true);
    setError(null);
    try {
      // 手动构造FormData
      const formData = new FormData();
      formData.append('comment', comment);
      const result = await submitComment(formData); 
      setMessage(result.message);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsPending(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>您的评论:</label>
        <input
          value={comment}
          onChange={(e) => setComment(e.target.value)}
          disabled={isPending}
        />
      </div>
      <button type="submit" disabled={isPending}>
        {isPending ? '提交中...' : '发表评论'}
      </button>
      {error && <p className="error">{error}</p>}
      {message && <p className="success">{message}</p>}
    </form>
  );
}

其实看到这里一眼就看出来优劣势了,react18中定义的变量实在太多了!

能力 React18方案 React19方案 优势
状态变量数量 需4个useState 1个useActionState统一管理 减少75%状态代码
子组件访问状态 需通过props传递isPending 子组件直接使用useFormStatus获取 解除父子组件耦合
表单数据收集 需onChange+useState 自动通过FormData获取 省去手动绑定
错误处理逻辑 分散在try/catch中 集中在Server Action返回 更易维护
代码量 ~35行 ~25行 减少30%+

5 最佳实践与升级建议

  1. 何时使用
    • 简单表单:直接使用useActionState + 原生form表单
    • 复杂UI:拆分组件 + useFormStatus获取局部状态
  2. 迁移(注意之前的useFormState被useActionState取代)
javascript 复制代码
// 从React 18升级到19:
- import { useFormState } from 'react-dom'; // 废弃
+ import { useActionState } from 'react';   // 新API

- const [state, formAction] = useFormState(action, initialState);
+ const [state, formAction, isPending] = useActionState(action, initialState);
  1. 注意事项 * useFormStatus 必须 是<form>的直接子组件 * 避免在onSubmit中调用e.preventDefault()(会阻止Action执行)

通过上述模式,React 19将表单开发从手动管理推向声明式协作,覆盖了90%的日常表单场景,大幅提升开发效率与应用性能。

相关推荐
止观止3 小时前
React虚拟DOM的进化之路
前端·react.js·前端框架·reactjs·react
谢尔登3 小时前
【React Natve】NetworkError 和 TouchableOpacity 组件
前端·react.js·前端框架
G等你下课6 小时前
React 路由懒加载入门:提升首屏性能的第一步
前端·react.js·前端框架
谢尔登6 小时前
【React Native】ScrollView 和 FlatList 组件
javascript·react native·react.js
FogLetter8 小时前
深入浅出React-Router-Dom:从前端路由到SPA架构的华丽转身
前端·react.js
Dream耀8 小时前
useReducer:React界的"灭霸手套",一个dispatch搞定所有状态乱局
前端·javascript·react.js
前端设计诗9 小时前
对React官网《Virtual DOM 及内核》注解:其实Virtual DOM 就藏在在代码行间
前端·react.js·前端框架
遂心_10 小时前
React初学者必备:用“状态管家”Reducer轻松管理复杂状态!
前端·javascript·react.js
FanetheDivine10 小时前
解决@ant-design/icons导致的样式异常
react.js·ant design