794 lines
22 KiB
Plaintext
794 lines
22 KiB
Plaintext
---
|
||
title: React 访谈中的表单
|
||
description: 构建交互式 React 表单的指南,涵盖受控与非受控组件、各种输入类型、复杂的状态管理以及强大的错误处理和验证策略
|
||
---
|
||
|
||
React 中的表单是构建交互式应用程序的关键部分,允许用户输入和提交数据。在 React 中,使用状态控制表单元素很常见,这使得它们既动态又可预测。
|
||
|
||
## 受控与非受控表单组件
|
||
|
||
在 React 中,表单输入可以通过两种方式管理:**受控**和**非受控**。主要区别在于表单数据的处理方式。
|
||
|
||
### 受控表单组件
|
||
|
||
在**受控表单组件**中,React 管理表单元素的状态。输入的 value 存储在状态变量中,并通过 `onChange` 处理程序进行更新。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function ControlledForm() {
|
||
const [name, setName] = useState('');
|
||
|
||
function handleChange(event) {
|
||
setName(event.target.value);
|
||
}
|
||
|
||
function handleSubmit(event) {
|
||
event.preventDefault();
|
||
alert(`Submitted Name: ${name}`);
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit}>
|
||
<label>
|
||
Name:
|
||
<input type="text" value={name} onChange={handleChange} />
|
||
</label>
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
在受控组件中,输入值存储在 React 状态 (`name`) 中。`onChange` 处理程序在用户输入时更新状态。这确保了该值始终由 React 控制。
|
||
|
||
### 非受控表单组件
|
||
|
||
在**非受控表单组件**中,表单元素的值由 DOM 本身管理,而不是 React 状态。
|
||
|
||
```jsx
|
||
import { useRef } from 'react';
|
||
|
||
function UncontrolledForm() {
|
||
const nameRef = useRef();
|
||
|
||
function handleSubmit(event) {
|
||
event.preventDefault();
|
||
|
||
// Access form values using `FormData`
|
||
const formData = new FormData(event.target);
|
||
console.log('Name:', formData.get('name'));
|
||
|
||
// Alternatively, access the <input> via a ref
|
||
console.log('Name:', nameRef.current.value);
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit}>
|
||
<label>
|
||
Name:
|
||
<input type="text" name="name" ref={nameRef} />
|
||
</label>
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
在非受控组件中,输入值未存储在 React 状态中。要访问提交时的表单数据,我们可以:
|
||
|
||
1. **使用 `FormData`**:直接从 `event.target` 访问表单元素,从表单创建 `FormData` 实例,并使用 `formData.get('name')` 检索值(对应于 `<input>` 上的 `name` 属性)
|
||
2. **使用 refs**:使用 `useRef()` 直接引用 `<input>` 字段。可以通过 `nameRef.current.value` 访问输入值
|
||
|
||
### 何时使用哪个?
|
||
|
||
| 特性 | 受控表单 | 非受控表单 |
|
||
| --- | --- | --- |
|
||
| **存储状态的位置** | React 状态 (`useState`) | 原生 DOM |
|
||
| **性能** | 需要在更新时重新渲染,可能导致大型表单出现问题 | 对于简单用例更有效率 |
|
||
| **验证** | 易于实现 | 需要手动验证 |
|
||
| **表单重置** | 容易 (`setState("")`) | 需要 `ref.current.value = ""` 或触发 `'reset'` 事件 |
|
||
| **用例** | 动态表单、验证、实时更新 | 简单表单、文件上传或集成非 React 代码 |
|
||
|
||
* 当您需要动态验证、操作或跟踪用户输入(例如,根据先前的响应切换某些表单字段的可见性)或嵌套表单状态时,请使用**受控表单组件**
|
||
* 当使用大型表单、与非 React 代码集成或优化性能时,请使用**非受控表单组件**
|
||
|
||
在面试中,考虑到表单通常没有很多字段,受控组件和非受控方法都是可行的。就我个人而言,我们倾向于使用受控组件,因为您可能会被要求重置值、添加输入时验证等,并且它更容易实现。
|
||
|
||
## 处理不同的输入类型
|
||
|
||
以下是使用受控方法的各种输入类型的代码示例,这意味着它们的值存储在状态中,并通过 `onChange` 处理程序进行更新。
|
||
|
||
### 文本输入
|
||
|
||
受控文本输入会在用户输入时更新其在状态中的值。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function TextInputExample() {
|
||
const [text, setText] = useState('');
|
||
|
||
return (
|
||
<div>
|
||
<label htmlFor="name-input">Name</label>
|
||
<input
|
||
id="name-input"
|
||
type="text"
|
||
value={text}
|
||
onChange={(event) => setText(event.target.value)}
|
||
/>
|
||
<p>Entered Text: {text}</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 输入字段的值由 React 状态 (`text`) 控制
|
||
* `onChange` 事件使用 `setText(event.target.value)` 更新状态
|
||
* 这确保了 React 动态管理输入值
|
||
|
||
`type` 属性有许多其他基于文本的值,每个值都有不同的用途:
|
||
|
||
| `type` | 用途 | 内置验证 |
|
||
| --- | --- | --- |
|
||
| `text` | 常规文本输入 | 否 |
|
||
| `number` | 数字文本输入 | 是,仅允许数字。通过 `min`/`max` 属性进行其他验证 |
|
||
| `email` | 电子邮件地址 | 是,必须包含 `@` |
|
||
| `password` | 安全密码输入 | 否,但输入被屏蔽 |
|
||
| `search` | 带有清除按钮的搜索字段 | 否 |
|
||
| `tel` | 电话号码 | 否。通过 `pattern` 属性进行其他验证 |
|
||
| `url` | URL | 是,必须以 `http://` 或 `https://` 开头 |
|
||
| `datetime-local` | 日期和时间选择 | 是 |
|
||
| `color` | 颜色选择器 | 是 |
|
||
|
||
**建议:**
|
||
|
||
* 将 **`text`** 用于通用输入字段
|
||
* 当您只需要数值和数字的内置验证时,使用 **`number`**
|
||
* 使用 **`email`**、**`url`** 和 **`tel`** 来利用内置验证
|
||
* 使用 **`password`** 进行安全文本输入
|
||
* 使用 **`search`** 进行针对搜索优化的字段
|
||
* 当需要日期和时间选择时,使用 **`datetime-local`**
|
||
* 使用 **`color`** 进行颜色选择
|
||
|
||
### 复选框输入
|
||
|
||
复选框是一个**布尔值**(选中或未选中)。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function CheckboxExample() {
|
||
const [isChecked, setIsChecked] = useState(false);
|
||
|
||
return (
|
||
<div>
|
||
<input
|
||
id="checkbox-input"
|
||
type="checkbox"
|
||
checked={isChecked}
|
||
onChange={(event) => setIsChecked(event.target.checked)}
|
||
/>
|
||
<label htmlFor="checkbox-input">Agree to terms and conditions</label>
|
||
<p>Checkbox is {isChecked ? 'checked' : 'unchecked'}</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
* `checked` 属性绑定到 `isChecked` 状态
|
||
* `onChange` 事件使用 `setIsChecked(event.target.checked)` 更新状态
|
||
* 这允许 React 跟踪复选框的选中状态
|
||
|
||
### 单选组
|
||
|
||
当从**多个选项中选择一个选项**时,使用单选按钮。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function RadioGroupExample() {
|
||
const [gender, setGender] = useState('');
|
||
|
||
return (
|
||
<div>
|
||
<div>
|
||
<input
|
||
id="radio-male"
|
||
type="radio"
|
||
name="gender"
|
||
value="male"
|
||
checked={gender === 'male'}
|
||
onChange={(event) => setGender(event.target.value)}
|
||
/>
|
||
<label htmlFor="radio-male">Male</label>
|
||
</div>
|
||
<div>
|
||
<input
|
||
id="radio-female"
|
||
type="radio"
|
||
name="gender"
|
||
value="female"
|
||
checked={gender === 'female'}
|
||
onChange={(event) => setGender(event.target.value)}
|
||
/>
|
||
<label htmlFor="radio-female">Female</label>
|
||
</div>
|
||
<p>Selected gender: {gender}</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
* `name="gender"` 确保只能选择一个选项
|
||
* `checked` 属性检查当前值是否与状态匹配 (`gender === "male"` 或 `"female"`)
|
||
* `onChange` 使用选定值更新状态
|
||
|
||
### 文本区域
|
||
|
||
`textarea` 用于多行文本输入。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function TextAreaExample() {
|
||
const [bio, setBio] = useState('');
|
||
|
||
return (
|
||
<div>
|
||
<label htmlFor="bio-input">Bio:</label>
|
||
<textarea
|
||
id="bio-input"
|
||
value={bio}
|
||
onChange={(event) => setBio(event.target.value)}
|
||
/>
|
||
<p>Bio Preview: {bio}</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 与 HTML 不同,React 使用 `value` 而不是在 `<textarea>` 中设置文本
|
||
* `onChange` 使用 `setBio(event.target.value)` 更新状态,确保 React 控制输入
|
||
|
||
### 选择下拉菜单
|
||
|
||
`<select>` 下拉菜单允许用户选择一个选项。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function SelectExample() {
|
||
const [fruit, setFruit] = useState('apple');
|
||
|
||
return (
|
||
<div>
|
||
<label htmlFor="favorite-fruit">Favorite fruit</label>
|
||
<select
|
||
id="favorite-fruit"
|
||
value={fruit}
|
||
onChange={(event) => setFruit(event.target.value)}>
|
||
<option value="apple">Apple</option>
|
||
<option value="banana">Banana</option>
|
||
<option value="orange">Orange</option>
|
||
</select>
|
||
<p>Selected fruit: {fruit}</p>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
* `value` 属性绑定到状态 (`fruit`)
|
||
* 当用户选择一个选项时,`onChange` 更新状态
|
||
* 这确保了下拉菜单的值由 React 控制
|
||
|
||
### 总结
|
||
|
||
| 输入类型 | 关键元素 | 关键值属性 | 状态更新 |
|
||
| --- | --- | --- | --- |
|
||
| 文本输入 | `<input>` | `value` | `setState(event.target.value)` |
|
||
| 复选框输入 | `<input>` | `checked` | `setState(event.target.checked)` |
|
||
| 单选组 | `<input>` | `checked` | `setState(event.target.value)` |
|
||
| 文本区域 | `<textarea>` | `value` | `setState(event.target.value)` |
|
||
| 选择下拉菜单 | `<select>` | `value` | `setState(event.target.value)` |
|
||
|
||
## 处理复杂的表单状态
|
||
|
||
在 React 中使用复杂表单时,有效地管理状态对于确保良好的性能和可维护性至关重要。当表单包含多个输入字段、动态字段添加、嵌套结构或高级验证时,表单会变得复杂。以下是一些有效处理复杂表单状态的策略。
|
||
|
||
### 使用 `useReducer` 处理复杂表单
|
||
|
||
对于具有多个字段和状态依赖关系的表单,`useReducer` 提供了一种结构化的方式来管理状态更新。
|
||
|
||
```jsx
|
||
import { useReducer } from 'react';
|
||
|
||
// Define reducer function
|
||
function formReducer(state, action) {
|
||
switch (action.type) {
|
||
case 'UPDATE_FIELD':
|
||
return { ...state, [action.field]: action.value };
|
||
case 'RESET':
|
||
return initialState;
|
||
default:
|
||
return state;
|
||
}
|
||
}
|
||
|
||
// Initial form state
|
||
const initialState = {
|
||
name: '',
|
||
email: '',
|
||
age: '',
|
||
};
|
||
|
||
function ComplexFormWithReducer() {
|
||
const [state, dispatch] = useReducer(formReducer, initialState);
|
||
|
||
function handleChange(event) {
|
||
dispatch({
|
||
type: 'UPDATE_FIELD',
|
||
field: event.target.name,
|
||
value: event.target.value,
|
||
});
|
||
}
|
||
|
||
function handleReset() {
|
||
return dispatch({ type: 'RESET' });
|
||
}
|
||
|
||
return (
|
||
<form>
|
||
<input
|
||
type="text"
|
||
name="name"
|
||
value={state.name}
|
||
onChange={handleChange}
|
||
placeholder="Name"
|
||
/>
|
||
<input
|
||
type="email"
|
||
name="email"
|
||
value={state.email}
|
||
onChange={handleChange}
|
||
placeholder="Email"
|
||
/>
|
||
<input
|
||
type="number"
|
||
name="age"
|
||
value={state.age}
|
||
onChange={handleChange}
|
||
placeholder="Age"
|
||
/>
|
||
<button type="button" onClick={handleReset}>
|
||
Reset
|
||
</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 保持状态更新可预测且集中
|
||
* 适用于具有条件逻辑和依赖关系的表单
|
||
* 与 `useState` 相比,可防止不必要的重新渲染
|
||
|
||
### 处理动态字段
|
||
|
||
当表单允许用户**动态添加或删除字段**(例如,多个电子邮件输入)时,状态应该是一个数组
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function DynamicForm() {
|
||
const [fields, setFields] = useState([{ id: 1, value: '' }]);
|
||
|
||
function addField() {
|
||
setFields([...fields, { id: fields.length + 1, value: '' }]);
|
||
}
|
||
|
||
function removeField(id) {
|
||
setFields(fields.filter((field) => field.id !== id));
|
||
}
|
||
|
||
function handleChange(id, event) {
|
||
const newFields = fields.map((field) =>
|
||
field.id === id ? { ...field, value: event.target.value } : field,
|
||
);
|
||
setFields(newFields);
|
||
}
|
||
|
||
return (
|
||
<form>
|
||
{fields.map((field) => (
|
||
<div key={field.id}>
|
||
<input
|
||
type="text"
|
||
value={field.value}
|
||
onChange={(event) => handleChange(field.id, event)}
|
||
placeholder="Enter value"
|
||
/>
|
||
<button type="button" onClick={() => removeField(field.id)}>
|
||
Remove
|
||
</button>
|
||
</div>
|
||
))}
|
||
<button type="button" onClick={addField}>
|
||
Add Field
|
||
</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 适用于用户**可以添加多个条目**的表单(例如,多个电子邮件地址)
|
||
* 保持 UI 灵活,无需预定义的字段
|
||
|
||
### 使用表单库处理复杂表单
|
||
|
||
除了手动管理所有内容之外,像 [React Hook Form](https://react-hook-form.com/) 和 [Formik](https://formik.org/)(未维护)这样的库简化了复杂的表单状态处理。
|
||
|
||
```jsx
|
||
import { useForm, useFieldArray } from 'react-hook-form';
|
||
|
||
function FormWithReactHookForm() {
|
||
const { register, handleSubmit, control } = useForm({
|
||
defaultValues: { emails: [{ value: '' }] },
|
||
});
|
||
|
||
const { fields, append, remove } = useFieldArray({ control, name: 'emails' });
|
||
|
||
function onSubmit(data) {
|
||
console.log(data);
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit(onSubmit)}>
|
||
{fields.map((field, index) => (
|
||
<div key={field.id}>
|
||
<input {...register(`emails.${index}.value`)} placeholder="Email" />
|
||
<button type="button" onClick={() => remove(index)}>
|
||
Remove
|
||
</button>
|
||
</div>
|
||
))}
|
||
<button type="button" onClick={() => append({ value: '' })}>
|
||
Add Email
|
||
</button>
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* **减少重新渲染**,使用引用而不是状态
|
||
* **轻松验证**,内置支持 Zod、Yup 和 Joi 等验证库
|
||
* **高效处理动态字段**,使用 `useFieldArray`
|
||
|
||
### 处理嵌套表单结构
|
||
|
||
有时,表单具有嵌套对象(例如,包含街道、城市和邮政编码的地址)。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function NestedForm() {
|
||
const [form, setForm] = useState({
|
||
name: '',
|
||
address: { street: '', city: '', zip: '' },
|
||
});
|
||
|
||
function handleChange(event) {
|
||
const { name, value } = event.target;
|
||
setForm((prev) => ({
|
||
...prev,
|
||
address: { ...prev.address, [name]: value },
|
||
}));
|
||
}
|
||
|
||
return (
|
||
<form>
|
||
<input
|
||
type="text"
|
||
name="name"
|
||
placeholder="Name"
|
||
value={form.name}
|
||
onChange={(event) => setForm({ ...form, name: event.target.value })}
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="street"
|
||
placeholder="Street"
|
||
value={form.address.street}
|
||
onChange={handleChange}
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="city"
|
||
placeholder="City"
|
||
value={form.address.city}
|
||
onChange={handleChange}
|
||
/>
|
||
<input
|
||
type="text"
|
||
name="zip"
|
||
placeholder="ZIP Code"
|
||
value={form.address.zip}
|
||
onChange={handleChange}
|
||
/>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 帮助在一个对象中**干净地**管理相关数据
|
||
* 适用于**地址、个人资料或嵌套表单部分**
|
||
|
||
### 复杂表单状态的最佳实践
|
||
|
||
* 使用 `useReducer` 进行结构化状态更新
|
||
* 使用动态字段进行灵活的输入处理
|
||
* 利用 React Hook Form 或 Formik 等表单库来简化验证和状态管理
|
||
* 处理分组表单数据时使用嵌套对象
|
||
|
||
## 错误处理和验证策略
|
||
|
||
处理错误和验证用户输入对于在 React 表单中创建流畅的用户体验至关重要。验证可确保用户在提交前输入正确的数据,从而防止无效条目并减少后端错误。以下是有效处理验证和错误的各种策略。
|
||
|
||
### 使用 `useState` 进行基本的客户端验证
|
||
|
||
对于简单的表单,您可以使用**基于状态的验证**来在提交前检查用户输入。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function BasicValidationForm() {
|
||
const [email, setEmail] = useState('');
|
||
const [error, setError] = useState('');
|
||
|
||
function handleSubmit(event) {
|
||
event.preventDefault();
|
||
|
||
if (!email) {
|
||
setError('Email is required');
|
||
return;
|
||
}
|
||
|
||
setError(''); // Clear error if validation passes
|
||
alert(`Submitted: ${email}`);
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit}>
|
||
<label>
|
||
Email:
|
||
<input
|
||
type="email"
|
||
value={email}
|
||
onChange={(event) => setEmail(event.target.value)}
|
||
/>
|
||
</label>
|
||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 使用本地状态来跟踪错误
|
||
* 在验证失败时显示错误消息
|
||
* 简单但足以满足基本的验证需求
|
||
|
||
### 使用正则表达式验证输入字段
|
||
|
||
对于**结构化输入字段**(如电子邮件、电话号码或密码),**基于正则表达式的验证**很有用。
|
||
|
||
```jsx
|
||
function EmailValidationForm() {
|
||
const [email, setEmail] = useState('');
|
||
const [error, setError] = useState('');
|
||
|
||
const validateEmail = (email) => {
|
||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||
};
|
||
|
||
function handleSubmit(event) {
|
||
event.preventDefault();
|
||
|
||
if (!validateEmail(email)) {
|
||
setError('Please enter a valid email address');
|
||
return;
|
||
}
|
||
|
||
setError('');
|
||
alert(`Valid Email: ${email}`);
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit}>
|
||
<label>
|
||
Email:
|
||
<input
|
||
type="email"
|
||
value={email}
|
||
onChange={(event) => setEmail(event.target.value)}
|
||
/>
|
||
</label>
|
||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 使用正则表达式检查电子邮件是否遵循有效格式
|
||
* 为用户提供实时反馈
|
||
|
||
### 浏览器内置的 HTML5 验证
|
||
|
||
现代浏览器为某些输入类型提供了内置验证。
|
||
|
||
```jsx
|
||
function HTML5ValidationForm() {
|
||
return (
|
||
<form>
|
||
<label>
|
||
Email:
|
||
<input type="email" required />
|
||
</label>
|
||
<br />
|
||
<label>
|
||
Password (Min 6 characters):
|
||
<input type="password" minLength="6" required />
|
||
</label>
|
||
<br />
|
||
<label>
|
||
Phone (Numbers only):
|
||
<input type="tel" pattern="[0-9]{10}" required />
|
||
</label>
|
||
<br />
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* `required`:确保字段已填写
|
||
* `minLength`:限制输入长度
|
||
* `pattern`:直接在 HTML 中使用正则表达式
|
||
* **无需 JavaScript** 即可工作,提高性能
|
||
|
||
### 用于高效验证的 React Hook Form
|
||
|
||
对于复杂的表单,\**React Hook Form* 提供了优化的验证,并最大限度地减少了重新渲染。
|
||
|
||
```jsx
|
||
import { useForm } from 'React Hook Form;
|
||
|
||
function HookFormValidation() {
|
||
const {
|
||
register,
|
||
handleSubmit,
|
||
formState: { errors },
|
||
} = useForm();
|
||
|
||
const onSubmit = (data) => alert(JSON.stringify(data));
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit(onSubmit)}>
|
||
<label>
|
||
Email:
|
||
<input
|
||
type="email"
|
||
{...register('email', { required: 'Email is required' })}
|
||
/>
|
||
</label>
|
||
{errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
|
||
<label>
|
||
Password:
|
||
<input
|
||
type="password"
|
||
{...register('password', {
|
||
required: 'Password is required',
|
||
minLength: {
|
||
value: 6,
|
||
message: 'Password must be at least 6 characters',
|
||
},
|
||
})}
|
||
/>
|
||
</label>
|
||
{errors.password && (
|
||
<p style={{ color: 'red' }}>{errors.password.message}</p>
|
||
)}
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* **使用 refs 而不是 state**,最大限度地减少重新渲染
|
||
* **更好的性能**,适用于大型表单
|
||
* **内置错误处理**,使用 `formState.errors`
|
||
|
||
### 处理服务器端验证
|
||
|
||
即使使用客户端验证,后端验证也是必要的。您需要知道如何显示来自 API 响应的错误消息。
|
||
|
||
```jsx
|
||
import { useState } from 'react';
|
||
|
||
function ServerErrorHandlingForm() {
|
||
const [email, setEmail] = useState('');
|
||
const [error, setError] = useState('');
|
||
|
||
async function handleSubmit(event) {
|
||
event.preventDefault();
|
||
|
||
try {
|
||
setError(null);
|
||
|
||
const response = await fetch('/api/validate-email', {
|
||
method: 'POST',
|
||
body: JSON.stringify({ email }),
|
||
headers: { 'Content-Type': 'application/json' },
|
||
});
|
||
|
||
const data = await response.json();
|
||
if (!response.ok) {
|
||
throw new Error(data.message || 'Something went wrong');
|
||
}
|
||
|
||
alert('Submission successful!');
|
||
} catch (err) {
|
||
setError(err.message);
|
||
}
|
||
}
|
||
|
||
return (
|
||
<form onSubmit={handleSubmit}>
|
||
<label>
|
||
Email:
|
||
<input
|
||
type="email"
|
||
value={email}
|
||
onChange={(event) => setEmail(event.target.value)}
|
||
/>
|
||
</label>
|
||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||
<button type="submit">Submit</button>
|
||
</form>
|
||
);
|
||
}
|
||
```
|
||
|
||
* 使用 `fetch()` 在服务器端验证电子邮件
|
||
* 如果验证失败,则显示 API 错误消息
|
||
* 防止安全风险(例如,黑客绕过客户端验证)
|
||
|
||
## 面试的最佳实践
|
||
|
||
* 对于简单的表单,受控输入和非受控输入都是可行的
|
||
* 包装在 `<form>` 中并利用浏览器表单提交事件
|
||
* 尽可能使用 HTML5 验证,以减少重新渲染,从而获得更好的性能
|
||
* 显示清晰的错误消息,指导用户如何修复其输入
|
||
* 仅仅客户端验证是不够的。还需要服务器端验证以防止安全漏洞
|
||
* 如果允许使用第三方库(例如,在家庭作业中),React Hook Form 是一个很好的补充,它提供了许多有用的功能,可以帮助您同时实现更好的性能
|
||
|
||
## 面试中您需要知道的内容
|
||
|
||
* **非受控和受控表单**:有什么区别,如何使用,以及何时使用
|
||
* **表单控件**:能够构建使用各种输入类型的表单
|
||
* **复杂表单**:如何构建复杂表单和最佳实践
|
||
* **错误处理和验证**:如何使用原生浏览器方法和 React 方法验证表单并显示错误
|
||
|
||
## 练习题
|
||
|
||
**编码**:
|
||
|
||
* [生成表格](/questions/user-interface/generate-table/react?framework=react\&tab=coding)
|
||
* [联系表单](/questions/user-interface/contact-form/react?framework=react\&tab=coding)
|
||
* [抵押贷款计算器](/questions/user-interface/mortgage-calculator/react?framework=react\&tab=coding)
|
||
* [温度转换器](/questions/user-interface/temperature-converter/react?framework=react\&tab=coding)
|
||
* [航班预订器](/questions/user-interface/flight-booker/react?framework=react\&tab=coding)
|
||
* [身份验证代码输入](/questions/user-interface/auth-code-input/react?framework=react\&tab=coding)
|