React 表单的秘密生活:用验证超能力控制不可控的东西!(React 第6天)

发布: (2026年1月14日 GMT+8 12:30)
10 min read
原文: Dev.to

Source: Dev.to

为什么表单很重要:快速现实检查

想象一下:你正在构建一个出色的电子商务应用。用户需要注册、登录或使用支付信息结账。如果没有稳健的表单处理,数据会丢失,错误会随机出现,用户会离开。

在 React 中,表单不仅仅是 HTML 标签——它们是 动态、状态驱动的强大工具。我们会覆盖基础知识,但会加入真实场景的变化,例如在用户注册场景中,糟糕的验证可能会把无效的电子邮件泄漏到你的数据库中。听起来很有趣,对吧?


Source:

受控组件 vs. 非受控组件:激烈的争论

在 React 中,表单元素(如 <input>)可以是 受控非受控

类型描述优点缺点
受控组件React 完全掌控。你将输入的 value 绑定到 useState 状态,并在每次变化时通过 onChange 处理函数更新它。• 完全控制
• 易于验证
• 实时反馈
• 代码稍多
• 大型表单可能出现性能问题
非受控组件让 DOM 以传统方式处理。使用 refs(例如 useRef)在提交时获取值。• 对快速表单更简洁
• 重渲染更少
• 难以进行实时验证
• “React 风格”较弱

场景 – 想象一个搜索框:

  • 受控? 在用户输入时更新建议。
  • 非受控? 在按下 Enter 时才获取查询——对基本需求可以,但会失去即时的交互魔法。

受控组件示例

import { useState } from 'react';

function ControlledInput() {
  const [value, setValue] = useState('');
  return (
    <input
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Type something..."
    />
  );
}

非受控组件示例

import { useRef } from 'react';

function UncontrolledInput() {
  const inputRef = useRef(null);
  const handleSubmit = () => console.log(inputRef.current?.value);
  return (
    <>
      <input ref={inputRef} placeholder="Type something..." />
      <button onClick={handleSubmit}>Log Value</button>
    </>
  );
}

图示 – 可视化流程:

受控 vs 非受控 组件在 React 中的对比

常见陷阱: 忘记在受控模式下处理 onChange——输入框会变成只读!

UX 小贴士: 对需要实时更新的功能(如密码强度指示器)使用受控组件。

表单处理:从输入到提交

处理表单主要涉及 事件状态。从 <form> 元素开始,添加输入框,并在表单上挂上 onSubmit 处理函数。使用 e.preventDefault() 阻止默认的页面刷新。

实时示例 – 登录表单

import { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    // API call here
    console.log({ email, password });
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit">Login</button>
    </form>
  );
}

陷阱: 提交后未重置状态——用户会看到旧数据。

用户体验提升: 在提交期间添加加载指示器,营造专业感。


验证策略:别让错误数据破坏派对

验证是表单变得严肃的地方。常见策略包括:

  • Inline – 用户输入时即时验证。
  • On blur – 当字段失去焦点时进行验证。
  • On submit – 用户尝试提交时进行验证。

简单的原生验证示例

import { useState } from 'react';

function SignupForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!email.includes('@')) newErrors.email = 'Invalid email!';
    if (password.length < 6) newErrors.password = 'Password too short!';
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      // Proceed with API call
      console.log('Form is valid');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      {errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}

      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      {errors.password && <p style={{ color: 'red' }}>{errors.password}</p>}

      <button type="submit">Sign Up</button>
    </form>
  );
}

验证实际效果的示意图:

Illustration of validation in action

陷阱: 过度验证 – 不要在每一次按键时都提醒用户。

UX 小贴士: 用红色显示友好的错误信息,并配以图标提升可读性。

与流行库的比较:原生 vs. 优点

为什么要重新发明轮子?库可以让生活更轻松。

优点缺点适用场景
Vanilla React• 轻量,无依赖
• 对实现拥有完整控制
• 需要更多样板代码
• 必须手动处理许多细节
小型到中型表单,学习基础概念
Formik• 开箱即用的状态、验证和提交处理
• 生态完善(Yup 集成)
• 增加打包体积
• API 有学习曲线
字段众多的复杂表单
React Hook Form• 最小化重新渲染,性能极佳
• API 简洁,能很好地配合已有 UI 库
• 思维模型略有不同(默认非受控)大型表单、对性能要求高的应用
Redux‑Form (legacy)• 将表单状态集中在 Redux 中
• 适合已经大量使用 Redux 的项目
• 已被更新的库取代
• 可能导致 Redux store 体积膨胀
已经在使用 Redux‑Form 的旧项目

要点

  • 受控组件 能提供细粒度的控制,适合实时验证。
  • 非受控组件 设置快速,但会牺牲实时洞察。
  • 始终 阻止默认提交 并自行管理状态。
  • 选择一种 验证策略,在用户体验和数据完整性之间取得平衡。
  • 当表单变得复杂时,考虑使用 FormikReact Hook Form 等库来减少样板代码。

现在去构建坚固、用户友好的表单,让你的 React 应用闪耀吧! 🎉

Form Libraries Overview

Formik

  • 处理状态、验证、touched/dirty 标记。
  • 稍显笨重;通常与 Yup 搭配使用进行模式验证。
  • 适用于拥有众多字段的 复杂表单

React Hook Form

  • 性能极佳;默认使用非受控输入。
  • 简洁的验证 API;与原生 HTML 验证良好集成。
  • 如果你对 hooks 不熟悉,可能需要一点学习曲线。
  • 非常适合希望最小化重新渲染的 高性能应用

Example

/* Formik */
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';

/* React Hook Form */
import { useForm } from 'react-hook-form';

Scenario – 对于多步骤调查,React Hook Form 更出色,因为它需要的样板代码更少,重新渲染也更少。

观看对比视频: React Hook Form vs Formik (YouTube) (replace with a real video link)


可访问性最佳实践:面向所有人的表单

包容性很重要!始终为输入框配上正确的标签和 ARIA 属性,并正确管理焦点。

  • 为每个输入框添加标签<label htmlFor="...">(或将输入框包裹在 <label> 中)。
  • 错误处理:在无效字段上添加 aria-invalid="true"aria-describedby="error-id"
  • 键盘导航:确保逻辑的 Tab 顺序,避免焦点陷阱。

场景 – 屏幕阅读器用户在填写联系表单时,如果缺少适当的 ARIA 标记会感到迷失。

可视参考

React Native accessibility best practices for inclusive apps

常见错误: 忽视错误信息的颜色对比度。
用户体验提示: 使用 WAVEaxe 等工具进行测试,以捕捉对比度问题。


实时示例与总结

自己动手试试 – 在 CodeSandbox 上的已验证注册表单:
打开 CodeSandbox (如有需要请替换为真实链接)

在实际应用中(例如编辑用户资料),通常会结合:

  1. 受控输入以实现即时 UI 反馈。
  2. 在失焦或提交时进行校验。
  3. 可访问的标签和 ARIA 属性。
  4. 当表单规模扩大时使用表单库(Formik 或 React Hook Form)。

这是一段 表单奇旅!你构建过最棘手的表单是什么?在下方留下你的故事吧。

敬请期待 第 7 天:路由与导航冒险。祝编码愉快! 🌟

Back to Blog

相关文章

阅读更多 »

React 编码挑战:卡片翻转游戏

React 卡片翻转游戏 – 代码 tsx import './styles.css'; import React, { useState, useEffect } from 'react'; const values = 1, 2, 3, 4, 5; type Card = { id: numb...