You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core Fluent Validation:React/Angular中的验证错误处理实现

在Angular和React中实现实体关联的验证错误展示

不管是Angular还是React,核心逻辑和.NET Core MVC的asp-validator-for一致:绑定实体字段、定义验证规则、针对字段展示对应错误,下面分框架具体说明实现方式:


Angular 实现方式

Angular自带成熟的表单验证体系,分响应式表单(推荐)和模板驱动表单两种模式,都能实现字段级错误关联展示。

1. 响应式表单(复杂场景首选)

通过FormGroup绑定实体字段,集中管理验证规则,后端返回的错误也能轻松映射到对应控件。

组件类示例

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

// 实体模型
interface User {
  username: string;
  email: string;
  password: string;
}

@Component({
  selector: 'app-user-form',
  templateUrl: './user-form.component.html'
})
export class UserFormComponent implements OnInit {
  userForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    // 绑定实体字段并设置前端验证规则
    this.userForm = this.fb.group({
      username: ['', [Validators.required, Validators.minLength(3)]],
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
    });
  }

  // 快捷获取表单控件
  get f() { return this.userForm.controls; }

  onSubmit() {
    if (this.userForm.invalid) {
      // 触发表单所有控件的验证状态更新
      Object.values(this.userForm.controls).forEach(control => control.markAsTouched());
      return;
    }
    // 提交逻辑,this.userForm.value即为实体数据
    console.log('提交数据:', this.userForm.value);
  }

  // 处理后端返回的验证错误
  handleApiErrors(errorRes: any) {
    // 假设后端返回格式:{ errors: { username: ['用户名已存在'], email: ['邮箱格式错误'] } }
    const errors = errorRes.errors;
    Object.keys(errors).forEach(field => {
      const control = this.userForm.get(field);
      if (control) {
        // 将后端错误映射到控件的错误集合中
        control.setErrors({ serverError: errors[field][0] });
      }
    });
  }
}

模板示例

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label>用户名</label>
    <input type="text" formControlName="username" class="form-control">
    <!-- 展示对应字段的验证错误 -->
    <div *ngIf="f.username.touched && f.username.invalid" class="text-danger">
      <div *ngIf="f.username.errors?.['required']">用户名不能为空</div>
      <div *ngIf="f.username.errors?.['minlength']">用户名至少3个字符</div>
      <div *ngIf="f.username.errors?.['serverError']">{{ f.username.errors?.['serverError'] }}</div>
    </div>
  </div>

  <div class="form-group">
    <label>邮箱</label>
    <input type="email" formControlName="email" class="form-control">
    <div *ngIf="f.email.touched && f.email.invalid" class="text-danger">
      <div *ngIf="f.email.errors?.['required']">邮箱不能为空</div>
      <div *ngIf="f.email.errors?.['email']">请输入有效邮箱</div>
      <div *ngIf="f.email.errors?.['serverError']">{{ f.email.errors?.['serverError'] }}</div>
    </div>
  </div>

  <button type="submit" class="btn btn-primary">提交</button>
</form>

2. 模板驱动表单(简单场景)

通过ngModel直接绑定实体字段,用模板变量获取控件状态:

<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm.value)">
  <div class="form-group">
    <label>用户名</label>
    <input type="text" name="username" [(ngModel)]="user.username" 
           required minlength="3" #username="ngModel" class="form-control">
    <div *ngIf="username.touched && username.invalid" class="text-danger">
      <div *ngIf="username.errors?.['required']">用户名不能为空</div>
      <div *ngIf="username.errors?.['minlength']">用户名至少3个字符</div>
    </div>
  </div>

  <!-- 其他字段逻辑类似 -->
  <button type="submit" class="btn btn-primary">提交</button>
</form>

React 实现方式

React没有内置表单验证体系,可通过原生状态管理或第三方库实现字段级错误展示,常用库有React Hook Form、Formik。

1. 原生React + useState(基础实现)

手动管理字段状态和错误集合:

import { useState } from 'react';

// 实体初始值
const initialUser = { username: '', email: '', password: '' };

export default function UserForm() {
  const [user, setUser] = useState(initialUser);
  const [errors, setErrors] = useState({});

  // 输入变更处理
  const handleChange = (e) => {
    const { name, value } = e.target;
    setUser(prev => ({ ...prev, [name]: value }));
    // 输入时清除对应字段的旧错误
    if (errors[name]) setErrors(prev => ({ ...prev, [name]: '' }));
  };

  // 表单验证逻辑
  const validate = () => {
    const newErrors = {};
    if (!user.username.trim()) newErrors.username = '用户名不能为空';
    else if (user.username.length < 3) newErrors.username = '用户名至少3个字符';
    
    if (!user.email.trim()) newErrors.email = '邮箱不能为空';
    else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(user.email)) newErrors.email = '请输入有效邮箱';
    
    if (!user.password.trim()) newErrors.password = '密码不能为空';
    else if (user.password.length < 6) newErrors.password = '密码至少6个字符';

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      console.log('提交数据:', user);
    }
  };

  // 处理后端返回的错误
  const handleApiErrors = (apiErrors) => {
    // 假设后端返回格式:{ username: '用户名已存在', email: '邮箱已被注册' }
    setErrors(apiErrors);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div className="form-group">
        <label>用户名</label>
        <input
          type="text"
          name="username"
          value={user.username}
          onChange={handleChange}
          className={`form-control ${errors.username ? 'is-invalid' : ''}`}
        />
        {errors.username && <div className="text-danger">{errors.username}</div>}
      </div>

      <div className="form-group">
        <label>邮箱</label>
        <input
          type="email"
          name="email"
          value={user.email}
          onChange={handleChange}
          className={`form-control ${errors.email ? 'is-invalid' : ''}`}
        />
        {errors.email && <div className="text-danger">{errors.email}</div>}
      </div>

      <button type="submit" className="btn btn-primary">提交</button>
    </form>
  );
}

2. React Hook Form(高效简洁首选)

轻量表单库,内置验证和错误管理:

import { useForm } from 'react-hook-form';

export default function UserForm() {
  const { register, handleSubmit, formState: { errors }, setError } = useForm();

  const onSubmit = (data) => {
    console.log('提交数据:', data);
  };

  // 处理后端错误
  const setApiErrors = (apiErrors) => {
    Object.keys(apiErrors).forEach(field => {
      setError(field, { type: 'server', message: apiErrors[field] });
    });
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="form-group">
        <label>用户名</label>
        <input
          type="text"
          {...register('username', {
            required: '用户名不能为空',
            minLength: { value: 3, message: '用户名至少3个字符' }
          })}
          className={`form-control ${errors.username ? 'is-invalid' : ''}`}
        />
        {errors.username && <div className="text-danger">{errors.username.message}</div>}
      </div>

      <div className="form-group">
        <label>邮箱</label>
        <input
          type="email"
          {...register('email', {
            required: '邮箱不能为空',
            pattern: { value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '请输入有效邮箱' }
          })}
          className={`form-control ${errors.email ? 'is-invalid' : ''}`}
        />
        {errors.email && <div className="text-danger">{errors.email.message}</div>}
      </div>

      <button type="submit" className="btn btn-primary">提交</button>
    </form>
  );
}

3. Formik + Yup(声明式验证)

结合Yup定义验证规则,代码更结构化:

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

// 实体验证规则Schema
const UserSchema = Yup.object().shape({
  username: Yup.string().required('用户名不能为空').min(3, '用户名至少3个字符'),
  email: Yup.string().required('邮箱不能为空').email('请输入有效邮箱'),
  password: Yup.string().required('密码不能为空').min(6, '密码至少6个字符')
});

export default function UserForm() {
  return (
    <Formik
      initialValues={{ username: '', email: '', password: '' }}
      validationSchema={UserSchema}
      onSubmit={(values) => console.log('提交数据:', values)}
    >
      {({ errors, touched }) => (
        <Form>
          <div className="form-group">
            <label>用户名</label>
            <Field name="username" className={`form-control ${errors.username && touched.username ? 'is-invalid' : ''}`} />
            <ErrorMessage name="username" component="div" className="text-danger" />
          </div>

          <div className="form-group">
            <label>邮箱</label>
            <Field name="email" type="email" className={`form-control ${errors.email && touched.email ? 'is-invalid' : ''}`} />
            <ErrorMessage name="email" component="div" className="text-danger" />
          </div>

          <button type="submit" className="btn btn-primary">提交</button>
        </Form>
      )}
    </Formik>
  );
}

核心逻辑总结

所有实现方式的本质都是:

  1. 字段绑定:表单控件与实体字段双向同步
  2. 规则关联:为字段设置前端验证规则或映射后端错误
  3. 错误展示:在对应字段附近,根据触发条件(触碰、提交等)显示专属错误

内容的提问来源于stack exchange,提问作者kaan240

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:05:28