我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

useActionState()react@19的专属钩子,低版本React引入这个钩子,都会各种编译报各种莫名其妙的错误。它主要用于表示表单提交过程中的各种状态,实际上也可以理解为异步函数数据提交的过程优化。所以,useActionState()所极力要解决的问题,其实在以前早就被解决过了...

苏南大叔:React19新特性,如何理解表单提交钩子useActionState? - useactionstate
React19新特性,如何理解表单提交钩子useActionState?(图5-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0create-react-app@5.0.1react@19.0.0

确保 react@19

无论是什么时候使用create-react-app创建的项目,为了正确使用useActionState()钩子,在根目录下面直接再次安装React@19,是绝对正确的选择。(注意替换19.0.0字样)

npm i react@19.0.0 --save

如果继续使用React@18的话,在代码编写过程中,会有各种各样的莫名其妙的错误发生。各种undefined,并不会报错useActionState()钩子不存在之类的。

苏南大叔:React19新特性,如何理解表单提交钩子useActionState? - 莫名其妙的报错
React19新特性,如何理解表单提交钩子useActionState?(图5-2)

这个钩子的最主要的应用场景,是体现异步提交的时候,三种状态的切换过程:提交loading/提交成功/提交失败。和苏南大叔以前描述过的useRequest()等等钩子,非常相似:

苏南大叔:React19新特性,如何理解表单提交钩子useActionState? - react19没报错
React19新特性,如何理解表单提交钩子useActionState?(图5-3)

官方说明

官方文档地址:

useActionState is a Hook that allows you to update state based on the result of a form action.

const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);

Call useActionState at the top level of your component to create component state that is updated when a form action is invoked. You pass useActionState an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state and whether the Action is still pending. The latest form state is also passed to the function that you provided.

使用范例一

这个例子是官方文档里面的例子,useActionState()用起来挺牵强的,完全无感。并且导出函数formAction并不是用在form上的,而是绑定在<form>里面的input或者button上面的。

import { useActionState } from "react";
async function increment(previousState, formData) {
  return previousState + 1;
}
function App() {
  const [state, formAction] = useActionState(increment, 0);
  return (
    <form>
      {state}
      <button formAction={formAction}>Increment</button>
    </form>
  );
}
export default App;

功能上来说,是个经典的数字不断加一的例子。改成这个useActionState之后,就挺不好理解的了。不推荐使用。注意这里使用的写法是:formAction,定义在提交按钮上。所以,这个代码还可以写成:

import { useActionState } from "react";
async function increment(previousState, formData) {
  return previousState + 1;
}
function App() {
  const [state, formAction] = useActionState(increment, 0);
  return (
    <form action={formAction}>
      {state}
      <button>Increment</button>
    </form>
  );
}
export default App;

使用范例二

这个例子里面,使用的是是定义在form标签上的action了,看起来稍稍正常了一些。

主体代码

App.js

import React, { useActionState } from "react";
import { doSubmit } from "./SubmitAction";
function Form() {
  const [state, submitAction, isPending] = useActionState(doSubmit, {
    error: "初始化state",
  });
  return (
    <>
      <style>{"input{width:100px}.red{color:red}.green{color:green}"}</style>
      <form action={submitAction}>
        <input type="text" name="name" placeholder="用户名" />
        <button type="submit" disabled={isPending}>更新</button>
        <div>
          {isPending && <span>更新中...</span>}
          {state?.error && !isPending && (
            <span className="red">{state?.error}</span>
          )}
          {state?.message && !isPending && (
            <span className="green">{state?.message}</span>
          )}
        </div>
      </form>
    </>
  );
}
export default Form;

苏南大叔:React19新特性,如何理解表单提交钩子useActionState? - state状态值
React19新特性,如何理解表单提交钩子useActionState?(图5-4)

提交逻辑

数据提交逻辑分为两个支线,一个支线是模拟的提交过程,主要特点是时长可控。另外一个支线就是真实的提交到服务器端了,这里的formData,就是大家印象里面的那个FormData。它适合的enctype是:application/x-www-form-urlencoded

参考文章:

SubmitAction.js

async function doSubmitFake(formData) {
  await new Promise((resolve) => {
    setTimeout(resolve, 1500);
  }); // 模拟sleep/wait/isPending
  const name = formData.get("name");
  if (name === "") {
    return { error: "Name is required" }; // 状态200
  }
  return { message: "保存成功" };
}
async function doSubmitReal(formData) {
  const res = await fetch("/test.php", {
    method: "POST",
    body: formData, // body: JSON.stringify({ name: "sunan" }),
  }).catch((error) => {
    return { error: error.message };
  });
  return res.json();
}
async function doSubmit(previousState, formData) {
  console.log("上次返回值:", previousState);
  // return await doSubmitFake(formData);
  return await doSubmitReal(formData);
}
export { doSubmit, doSubmitFake, doSubmitReal };

配套express

配套的express逻辑代码如下:

var express = require("express");
var app = express();
let server = app.listen(3222, function () {
  let port = server.address().port;
  console.log(`访问地址为 http://localhost:${port}`);
});
const formidable = require("formidable");
app.post("*", (req, res) => {
  const form = new formidable.IncomingForm();
  form.parse(req, function (err, fields, files) {
    let name = fields["name"].toString().trim();
    if (name === "") {
      return res.json({ error: "用户名不能为空" });
    }
    res.json({ message: "ok," + name });
  });
});

这里的express使用formidable中间件,是和前端的FormData相匹配的。具体的情况,待后续讨论。参考文章:

苏南大叔:React19新特性,如何理解表单提交钩子useActionState? - 上一次状态
React19新特性,如何理解表单提交钩子useActionState?(图5-5)

和主体代码之间通过proxy进行连接。参考文章:

结语

苏南大叔的更多react经验文章,请参考:

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   react