Menu

React 表单:输入框、复选框、select 与校验

用受控输入框在 React 中构建表单:文本框、textarea、select、复选框和单选按钮,用一个状态对象管理多个字段,用 onSubmit 提交,显示校验错误,以及 React 19 的表单 action。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

React 表单把每个字段的值保存在状态中:输入框显示 value={state},并通过 onChange 更新它。用户提交时,onSubmit 处理函数调用 e.preventDefault() 阻止页面刷新,然后使用已经保存在状态中的值。

输入一个名字,按 Enter 或点击按钮。然后删掉 e.preventDefault(); 这一行:预览会显示浏览器在原生提交时会发送的字段,而在真实页面中,原生提交还会刷新页面。

值来自状态的输入框称为受控输入框。另一种做法是让 DOM 保存值、之后再读取,这在受控组件与非受控组件中有讲解。

文本输入框与 textarea

每个文本字段的工作方式都一样:value 来自状态,onChange 读取 e.target.value。<textarea> 也遵循同样的模式。在 HTML 中,它的文本写在标签之间,但在 React 中你要把它作为 value 传入。

因为值是你自己设置的,所以你可以在用户输入的内容显示之前修改它:标题字段会把每个字母都转成大写。字符计数器在每次按键时更新,因为 body 是状态,每次改动都会重新渲染。

Select

<select> 的 value 写在 select 元素本身上,而不是在某个 option 上写 selected。e.target.value 就是被选中 option 的 value。

对于多选下拉框,加上 multiple,并传入一个数组作为 value。用 Array.from(e.target.selectedOptions, (o) => o.value) 读取选中的项。

复选框与单选按钮

复选框用 checked 而不是 value 来控制,它的处理函数读取 e.target.checked,值为 true 或 false。同一组的单选按钮共用一个 name;当状态等于某个按钮的值时,它就被选中。

在复选框上读取 e.target.value 是个常见错误:对于没有 value 属性的复选框,无论是否勾选,它都返回字符串 "on"。

用一个状态对象管理多个字段

一个有十个字段的表单,不需要十个 useState 调用和十个处理函数。保存一个对象,给每个输入框一个与键名一致的 name,再写一个处理函数,把 name 作为计算属性名使用:[e.target.name]。

处理函数会检查输入框的类型,让复选框保存布尔值,其他字段保存文本。新增字段时,只需在初始对象中加一个键,再加一个 name 相同的输入框;处理函数无需修改。展开语法会保留其他所有字段(参见更新数组和对象)。

提交表单

把提交逻辑放在表单的 onSubmit 上,而不是按钮的 onClick 上。用户在文本字段中按 Enter 时,onSubmit 也会触发,而点击处理函数会漏掉这种情况。表单中的 <button> 默认是提交按钮;给其他按钮加上 type="button",它们就不会提交表单。

打开控制台,输入密码并按 Enter:表单会打印整个对象。点击“Clear”不会打印任何内容,因为它是 type="button"。删掉这个属性,Clear 也会开始提交表单。

校验与显示错误

简单的校验就是一个函数:接收这些值,返回一个由提示信息组成的对象。在提交时运行它,把结果存进状态,并在每个字段下方显示对应的信息。请求进行期间禁用提交按钮,这样双击就不会把表单发送两次。

在字段为空时点击 Save,就能看到两条提示信息,然后把它们填好。saveProfile 代替真实的请求:它等待 800 毫秒后 resolve。noValidate 属性关闭了浏览器自带的校验气泡,只显示你自己的提示信息;去掉它,再给某个输入框加上 required,就能对比效果。

错误也可以在渲染时计算,而不是保存起来,比如 const emailError = form.email && !form.email.includes('@'),这样会在用户输入时就显示提示。根据表单选择合适的时机:提交时校验更平和,输入时校验反馈更快。

React 19 中的表单 action

React 19 提供了另一种处理表单的方式。给表单的 action prop 传入一个函数。React 会替你阻止页面刷新,并用一个 FormData 对象调用这个函数,其中包含每个带有 name 的字段。这些字段根本不需要状态:它们是非受控的,action 完成后 React 会清空它们。

输入你自己的评论并发布。列表会显示你的文字,输入框则回到 Nice docs:action 完成后,React 会把非受控字段重置为它们的 defaultValue。action 可以是异步的,在它运行期间,React 会把这次更新当作一个过渡(transition)。

要显示 action 的等待状态或错误,可以用 useActionState 包装它,它会返回最新的结果、一个供表单使用的包装后的 action,以及一个 isPending 标志。表单深处的按钮也可以用 react-dom 中的 useFormStatus 读取等待状态:

import { useFormStatus } from 'react-dom';

function SubmitButton() {
    const { pending } = useFormStatus();
    return <button disabled={pending}>{pending ? 'Posting...' : 'Post'}</button>;
}

这里的编辑器没有加载 useFormStatus,所以它只出现在这个静态代码块中。它必须在渲染于 <form> 内部的组件中调用,而不是在渲染表单的那个组件中调用。

在 React 19 中,受控状态加 onSubmit,和表单 action 加 FormData,两种方式都可以用。受控字段适合在输入时就要做出反应的表单(实时校验、字符计数器、相互依赖的字段)。action 适合只在用户提交时才需要这些值的表单。

常见问题

在 React 中如何获取输入框的值?

把它存进状态,并在 onChange 中更新:<input value={name} onChange={e => setName(e.target.value)} />。这样当前文本始终保存在 name 中。复选框则读取 e.target.checked,而不是 e.target.value。

在 React 中如何阻止表单刷新页面?

在 onSubmit 处理函数的开头调用 e.preventDefault()。不调用的话,浏览器会执行普通的表单提交,跳转并重新加载页面。

在 React 中如何用一个状态管理多个输入框?

在状态中保存一个对象,给每个输入框一个与对象键名一致的 name,再用一个使用计算属性名的处理函数:setForm({ ...form, [e.target.name]: e.target.value })。

为什么 React 中的 textarea 用 value 而不是子元素?

在 HTML 中,textarea 的文本写在标签之间,但在 React 中你要把它作为 value(或 defaultValue)传入,和 input 一样。这样 React 表单里的每个文本字段工作方式都相同。

React 19 中的表单 action 是什么?

在 React 19 中,你可以给 <form action={fn}> 传入一个函数。React 会阻止页面刷新,用表单的 FormData 调用这个函数,并在完成后重置非受控字段。useActionState 在此基础上提供了 pending 标志和返回结果。

Coddy 编程语言插图

和 Coddy 一起学编程

开始