React19新特性,如何理解表单提交钩子useActionState?
发布于 作者:苏南大叔 来源:程序如此灵动~useActionState()
是react@19
的专属钩子,低版本React
引入这个钩子,都会各种编译报各种莫名其妙的错误。它主要用于表示表单提交过程中的各种状态,实际上也可以理解为异步函数数据提交的过程优化。所以,useActionState()
所极力要解决的问题,其实在以前早就被解决过了...
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0
,create-react-app@5.0.1
,react@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()
钩子不存在之类的。
这个钩子的最主要的应用场景,是体现异步提交的时候,三种状态的切换过程:提交loading
/提交成功/提交失败。和苏南大叔以前描述过的useRequest()
等等钩子,非常相似:
官方说明
官方文档地址:
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;
提交逻辑
数据提交逻辑分为两个支线,一个支线是模拟的提交过程,主要特点是时长可控。另外一个支线就是真实的提交到服务器端了,这里的formData
,就是大家印象里面的那个FormData
。它适合的enctype
是:application/x-www-form-urlencoded
。
参考文章:
- https://newsn.net/say/js-sleep-settimeout.html
- https://newsn.net/say/js-formdata.html
- https://newsn.net/say/js-formdata-2.html
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
相匹配的。具体的情况,待后续讨论。参考文章:
和主体代码之间通过proxy
进行连接。参考文章:
结语
苏南大叔的更多react
经验文章,请参考:
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。