Skip to content

探索原生 HTML 表单验证

在前端开发中,原生 HTML 表单验证提供了丰富的功能,但常常被忽视。很多开发者更习惯使用 JavaScript 来实现复杂的表单验证逻辑,然而原生的 HTML 表单验证机制能帮我们实现许多常见的需求。在本文中,我们将深入了解这些验证机制,讨论它们的优缺点,并展示一些实用的代码示例。

基础

HTML5 表单验证提供了多种验证机制,例如 required 属性、type 属性和 pattern 属性等。这些属性允许我们为输入字段设置约束条件,浏览器会在提交表单之前自动检查是否满足这些条件。如果某个字段不符合要求,浏览器会自动显示一条错误提示信息。

html
<form>
  <input type="text" name="username" required placeholder="请输入用户名" />
  <input type="email" name="email" required placeholder="请输入邮箱" />
  <input type="submit" value="提交" />
</form>
<form>
  <input type="text" name="username" required placeholder="请输入用户名" />
  <input type="email" name="email" required placeholder="请输入邮箱" />
  <input type="submit" value="提交" />
</form>

在这个示例中,required 属性确保用户名和邮箱字段不能为空,type="email" 则强制邮箱字段必须符合邮箱格式。如果用户未填写用户名或邮箱格式不正确,浏览器会自动阻止表单提交并显示错误信息。

自定义验证信息:setCustomValidity

有时,我们需要在默认验证之外添加自定义的错误提示。例如,我们希望在用户未输入内容时显示自定义的提示信息。此时可以使用 setCustomValidity 方法。

html
<input
  name="example"
  placeholder="请输入内容"
  onChange={(event) => {
    const input = event.currentTarget;
    if (input.value === "") {
      input.setCustomValidity("自定义提示:此字段不能为空");
    } else {
      input.setCustomValidity("");
    }
  }}
/>
<input
  name="example"
  placeholder="请输入内容"
  onChange={(event) => {
    const input = event.currentTarget;
    if (input.value === "") {
      input.setCustomValidity("自定义提示:此字段不能为空");
    } else {
      input.setCustomValidity("");
    }
  }}
/>

在这个例子中,我们通过监听 onChange 事件,在输入为空时调用 setCustomValidity 方法并设置自定义提示信息。当输入非空时,清空提示信息。

但setCustomValidity 存在一些不足。当输入字段最初是有效的,但在重置后为空时,按下提交按钮不会触发验证提示,导致用户体验不一致。

为了解决这些问题,我们可以创建一个自定义的验证组件,将验证逻辑封装起来,避免重复的代码和不一致的用户体验。

优化验证逻辑:自定义组件

为了解决上述问题,我们可以自定义封装 Input 组件。通过创建一个自定义 Input 组件,我们可以将验证逻辑集中到一起,减少重复代码,提高代码的可读性和可维护性。

js
import { useRef, useLayoutEffect } from "react";
 
function Input({ customValidity, ...props }) {
  const ref = useRef();
  useLayoutEffect(() => {
    if (customValidity != null) {
      const input = ref.current;
      input.setCustomValidity(customValidity);
    }
  }, [customValidity]);
 
  return <input ref={ref} {...props} />;
}
 
function Form() {
  const [value, setValue] = useState("");
  return (
    <form>
      <Input
        name="example"
        value={value}
        onChange={(event) => setValue(event.target.value)}
        customValidity={value.length ? "" : "自定义提示:此字段不能为空"}
      />
      <button>提交</button>
    </form>
  );
}
import { useRef, useLayoutEffect } from "react";
 
function Input({ customValidity, ...props }) {
  const ref = useRef();
  useLayoutEffect(() => {
    if (customValidity != null) {
      const input = ref.current;
      input.setCustomValidity(customValidity);
    }
  }, [customValidity]);
 
  return <input ref={ref} {...props} />;
}
 
function Form() {
  const [value, setValue] = useState("");
  return (
    <form>
      <Input
        name="example"
        value={value}
        onChange={(event) => setValue(event.target.value)}
        customValidity={value.length ? "" : "自定义提示:此字段不能为空"}
      />
      <button>提交</button>
    </form>
  );
}

通过这种方式,我们实现了一个具备自定义验证能力的输入组件。Input 组件会接收 customValidity 属性并在组件更新时自动执行验证。

复杂验证逻辑示例

在实际应用中,验证逻辑往往比简单的本地检查更复杂,甚至可能涉及异步验证。例如,我们需要检查用户名是否已被使用。这时可以结合 React Query 等库管理验证状态。

js
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { verifyUsername } from "./verifyUsername";
import { Input } from "./Input";
 
function Form() {
  const [value, setValue] = useState("");
  const { data, isLoading, isError } = useQuery({
    queryKey: ["verifyUsername", value],
    queryFn: () => verifyUsername(value),
    enabled: Boolean(value),
  });
 
  const validationMessage = data?.validationMessage;
 
  return (
    <form>
      <Input
        name="username"
        required={true}
        customValidity={
          isLoading
            ? "正在验证用户名..."
            : isError
            ? "验证失败"
            : validationMessage
        }
        value={value}
        onChange={(event) => {
          setValue(event.currentTarget.value);
        }}
      />
      <button>提交</button>
    </form>
  );
}
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { verifyUsername } from "./verifyUsername";
import { Input } from "./Input";
 
function Form() {
  const [value, setValue] = useState("");
  const { data, isLoading, isError } = useQuery({
    queryKey: ["verifyUsername", value],
    queryFn: () => verifyUsername(value),
    enabled: Boolean(value),
  });
 
  const validationMessage = data?.validationMessage;
 
  return (
    <form>
      <Input
        name="username"
        required={true}
        customValidity={
          isLoading
            ? "正在验证用户名..."
            : isError
            ? "验证失败"
            : validationMessage
        }
        value={value}
        onChange={(event) => {
          setValue(event.currentTarget.value);
        }}
      />
      <button>提交</button>
    </form>
  );
}

再让我们实现一个需要再次确认密码的表单验证逻辑:

js
import { useState } from "react";
import { Input } from "./Input";
 
function ConfirmPasswordForm() {
  const [password, setPassword] = useState("");
  const [confirmedPass, setConfirmedPass] = useState("");
 
  const matches = confirmedPass === password;
  return (
    <form>
      <Input
        type="password"
        name="password"
        required={true}
        value={password}
        onChange={(event) => {
          setPassword(event.currentTarget.value);
        }}
      />
      <Input
        type="password"
        name="confirmedPassword"
        required={true}
        value={confirmedPass}
        customValidity={matches ? "" : "Password must match"}
        onChange={(event) => {
          setConfirmedPass(event.currentTarget.value);
        }}
      />
      <button>Submit</button>
    </form>
  );
}
import { useState } from "react";
import { Input } from "./Input";
 
function ConfirmPasswordForm() {
  const [password, setPassword] = useState("");
  const [confirmedPass, setConfirmedPass] = useState("");
 
  const matches = confirmedPass === password;
  return (
    <form>
      <Input
        type="password"
        name="password"
        required={true}
        value={password}
        onChange={(event) => {
          setPassword(event.currentTarget.value);
        }}
      />
      <Input
        type="password"
        name="confirmedPassword"
        required={true}
        value={confirmedPass}
        customValidity={matches ? "" : "Password must match"}
        onChange={(event) => {
          setConfirmedPass(event.currentTarget.value);
        }}
      />
      <button>Submit</button>
    </form>
  );
}

总结

通过自定义的 <Input /> 组件,我们能够在 React 中实现一个更可靠、易于管理的验证方案。此组件在初始化和重置表单时自动处理验证逻辑,提升了用户体验和代码可维护性。希望这个方法能为你的项目带来更好的验证体验!

Updated Date:

Light tomorrow with today.