增强的表单操作
在 React 19 之前的版本中,表单的操作能够讲述的内容主要是受控组件和非受控组件。对于表单整体的操作基本上 React 是不怎么涉及的。但是在 React 19 中由于强化了 Server Component,对于表单的操作支持也开始变得有必要起来。
在不使用其他的支持库处理表单的时候,传统的表单操作大致都是这样的。
const LoginForm = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [isPending, setPending] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
setPending(true);
try {
await login(username, password);
} catch (error) {
setError(error);
} finally {
setPending(false);
}
},
[username, password]
);
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="usename">Username</label>
<input
type="text"
id="username"
name="username"
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{error && <div>{error}</div>}
<button type="submit" disabled={isPending}>
{isPending ? "Processing..." : "Login"}
</button>
</form>
);
};
在这个示例里,整个表单的处理看上去就十分的繁琐,但是其中每一个步骤却还都是十分必要的。但是在 React 19 引入了一个新的 Hook useActionState
以后,上面这个示例就可以被大大的简化了。
先来看示例。
const LoginForm = () => {
const [(state, formAction, isPending)] = useActionState(
async (prevState, formData) => {
try {
await login(formData.get("username"), formData.get("password"));
return { error: null };
} catch (error) {
return { error };
}
},
{ error: null }
);
return (
<form action={handleSubmit}>
<div>
<label htmlFor="usename">Username</label>
<input type="text" id="username" name="username" />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" />
</div>
{state.error && <div>{state.error}</div>}
<button type="submit" disabled={isPending}>
{isPending ? "Processing..." : "Login"}
</button>
</form>
);
};
可以看到在换用useActionState
以后,表单处理的整体代码两变少了,而且结构也更加清晰了。
useActionState
的语法格式为const [state, formAction, isPending] = useActionState(fn, initialState, permalLink?);
。在useActionState
返回的state
中,保存的是参数fn
执行返回的结果,如果表单还没有被提交,那么state
的内容就将是initialState
的内容。formAction
是useActionState
生成的用于提供给<form>
的action
属性或者是<button>
的formAction
属性使用的用于处理表单提交的函数。isPending
则是用来表示当前formAction
的执行状态的。
useActionState
中的使用到的fn
参数实际上就是在表单提交的时候实际调用的函数。这个函数参数要求的类型是(prevState: any, formData: FormData) => any
。从这个函数参数的类型可以看出来,表单在提交的时候会首先将当前的最新状态作为第一个参数传入,然后将传统表单数据作为第二个参数传入,这个函数执行结束的返回值将作为useActionState
的新状态。
这样结合上面的示例就可以彻底明白useActionState
中的各个元素都是如何使用的了。
useActionState
的第三个参数permalink
是在服务器渲染处理中用来结合渐进增强处理使用的,如果表单在Javascript包夹在之前提交,那么浏览器将会导航到permalink
指定的URL。在表单被激活以后,permalink
将会失效。
<form>
的action
和onSubmit
的区别
action
是 React DOM 19 中新引入的属性,主要用来支持表单的提交操作。与onSubmit
最大的一个不同是onSubmit
仅支持在客户端处理表单,而action
则支持在客户端和服务端均支持表单的提交处理。
此外就是action
会传递当前表单的状态和表单提交数据给处理函数,而onSubmit
传递的则是表单提交事件。
useFormStatus
useFormStatus
这个 Hook 是 React DOM 19 中提供的,用来获取当前表单的提交状态的,它的调用不接受任何参数,但是会返回一系列表单相关的属性,其常用格式为const {pending, data, method, action} = useFormStatus()
。
这个 Hook 对于实现一个独立的提交功能很方便。例如可以如同下面这个示例中一样实现一个独立的提交按钮。
const Submit = () => {
const { pending } = useFormStatus();
return <button disabled={pending}>Submit</button>;
};
function App() {
const [state, action] = useActionState(
async (prev, formData) => {
// 省略表单数据的处理
},
{
error: null,
}
);
return (
<form action={action}>
<Submit />
</form>
);
}