form-validation-architect

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Form Validation Architect

表单验证架构师

Expert in building production-grade form systems with client-side validation, type safety, and excellent UX.
专注于构建具备客户端验证、类型安全和出色用户体验的生产级表单系统。

When to Use

适用场景

Use for:
  • Complex forms with multiple fields and validation rules
  • Multi-step wizards with progress tracking
  • Dynamic field arrays (add/remove items)
  • Form state persistence across sessions
  • Async validation (check username availability, validate address)
  • Dependent fields (enable B when A is checked)
  • File uploads with progress and validation
  • Autosave and optimistic updates
NOT for:
  • Simple contact forms (HTML + basic JS is fine)
  • Backend-only validation (use Joi, Yup on server)
  • Non-React frameworks (use Formik alternatives)
  • Read-only displays (no form needed)
适用场景:
  • 包含多个字段和验证规则的复杂表单
  • 带进度追踪的多步骤向导
  • 动态字段数组(添加/删除项)
  • 跨会话的表单状态持久化
  • 异步验证(检查用户名可用性、验证地址等)
  • 依赖字段(当A被勾选时启用B)
  • 带进度和验证的文件上传
  • 自动保存和乐观更新
不适用场景:
  • 简单联系表单(HTML + 基础JS即可满足)
  • 仅后端验证(在服务端使用Joi、Yup)
  • 非React框架(使用Formik替代方案)
  • 只读展示(无需表单)

Quick Decision Tree

快速决策树

Does your form:
├── Have >5 fields? → Use react-hook-form
├── Need type safety? → Add Zod schemas
├── Have dynamic fields? → Use field arrays
├── Span multiple steps? → Use wizard pattern
├── Need async validation? → Use resolver + async rules
└── Just email/message? → Use native HTML validation

你的表单是否:
├── 包含5个以上字段?→ 使用react-hook-form
├── 需要类型安全?→ 添加Zod schema
├── 包含动态字段?→ 使用字段数组
├── 分为多个步骤?→ 使用向导模式
├── 需要异步验证?→ 使用解析器+异步规则
└── 仅包含邮箱/消息?→ 使用原生HTML验证

Technology Selection (2024+)

技术选型(2024+)

React Hook Form (Recommended)

React Hook Form(推荐)

Why RHF over Formik:
  • Performance: Uncontrolled inputs → fewer re-renders
  • Bundle size: 8KB vs 30KB (Formik)
  • DevEx: Better TypeScript support
  • Adoption: 40k+ stars, industry standard 2023+
Timeline:
  • 2015-2019: Formik dominated
  • 2019: React Hook Form released
  • 2022+: RHF became standard
  • 2024: Formik in maintenance mode
为什么选择RHF而非Formik:
  • 性能: 非受控输入 → 更少重渲染
  • 包体积: 8KB vs 30KB(Formik)
  • 开发体验: 更好的TypeScript支持
  • 行业采用度: 40k+星标,2023+成为行业标准
时间线:
  • 2015-2019: Formik占据主导
  • 2019: React Hook Form发布
  • 2022+: RHF成为标准
  • 2024: Formik进入维护模式

Zod for Schema Validation

Zod Schema验证

Why Zod over Yup:
  • TypeScript-first: Infer types from schemas
  • Composability: Better schema reuse
  • Error messages: More customizable
  • Modern: Active development, latest features
Timeline:
  • 2017-2020: Yup standard
  • 2020: Zod released
  • 2023+: Zod preferred for new projects

为什么选择Zod而非Yup:
  • TypeScript优先: 从schema推断类型
  • 可组合性: 更好的schema复用性
  • 错误提示: 更高的自定义性
  • 现代化: 活跃开发,支持最新特性
时间线:
  • 2017-2020: Yup是标准
  • 2020: Zod发布
  • 2023+: 新项目优先选择Zod

Common Anti-Patterns

常见反模式

Anti-Pattern 1: Controlled Inputs Everywhere

反模式1:全受控输入

Novice thinking: "All form inputs should be controlled with useState"
Problem: Causes re-render on every keystroke
Wrong approach:
typescript
// ❌ Re-renders entire component on every keystroke
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
// ... 20 more useState calls

<input value={email} onChange={(e) => setEmail(e.target.value)} />
Correct approach:
typescript
// ✅ Uncontrolled with react-hook-form (minimal re-renders)
const { register, handleSubmit } = useForm();

<input {...register('email')} />
<input {...register('password')} />
<input {...register('name')} />
Why it matters: Forms with 10+ fields become sluggish with controlled inputs.

新手误区: "所有表单输入都应该用useState做受控处理"
问题: 每次按键都会触发重渲染
错误示例:
typescript
// ❌ 每次按键都会触发整个组件重渲染
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
// ... 另外20个useState调用

<input value={email} onChange={(e) => setEmail(e.target.value)} />
正确示例:
typescript
// ✅ 使用react-hook-form的非受控输入(最少重渲染)
const { register, handleSubmit } = useForm();

<input {...register('email')} />
<input {...register('password')} />
<input {...register('name')} />
重要性: 包含10+字段的表单使用受控输入会变得卡顿。

Anti-Pattern 2: String-Based Validation

反模式2:基于字符串的验证

Problem: No type safety, easy to make mistakes
Wrong approach:
typescript
// ❌ String validation, no types
const validate = (values) => {
  if (!values.email.includes('@')) return 'Invalid email';
  if (values.age < 18) return 'Must be 18+';
  // Typo in field name? Runtime error!
};
Correct approach:
typescript
// ✅ Zod schema with type inference
const schema = z.object({
  email: z.string().email('Invalid email'),
  age: z.number().min(18, 'Must be 18+'),
  username: z.string()
    .min(3, 'Too short')
    .regex(/^[a-z0-9_]+$/, 'Lowercase, numbers, underscores only')
});

type FormData = z.infer<typeof schema>; // Automatic TypeScript type!
Timeline:
  • Pre-2020: String-based validation common
  • 2020+: Schema-first validation standard
  • 2024: Type inference from schemas expected

问题: 无类型安全,容易出错
错误示例:
typescript
// ❌ 字符串验证,无类型
const validate = (values) => {
  if (!values.email.includes('@')) return 'Invalid email';
  if (values.age < 18) return 'Must be 18+';
  // 字段名拼写错误?运行时错误!
};
正确示例:
typescript
// ✅ 带类型推断的Zod schema
const schema = z.object({
  email: z.string().email('Invalid email'),
  age: z.number().min(18, 'Must be 18+'),
  username: z.string()
    .min(3, 'Too short')
    .regex(/^[a-z0-9_]+$/, 'Lowercase, numbers, underscores only')
});

type FormData = z.infer<typeof schema>; // 自动生成TypeScript类型!
时间线:
  • 2020年前: 基于字符串的验证很常见
  • 2020+: 优先使用schema-first验证
  • 2024: 从schema推断类型成为标配

Anti-Pattern 3: No Error State Management

反模式3:错误状态管理缺失

Problem: Errors shown before user interacts
Wrong approach:
typescript
// ❌ Shows errors immediately on page load
{errors.email && <span>{errors.email}</span>}
Correct approach:
typescript
// ✅ Show errors only after field is touched
const { formState: { errors, touchedFields } } = useForm();

{touchedFields.email && errors.email && (
  <span className="error">{errors.email.message}</span>
)}

// Or: Use mode="onBlur" to validate on blur
const form = useForm({
  mode: 'onBlur' // Validate when user leaves field
});
Why it matters: Better UX → user isn't yelled at before typing

问题: 用户未交互前就显示错误
错误示例:
typescript
// ❌ 页面加载后立即显示错误
{errors.email && <span>{errors.email}</span>}
正确示例:
typescript
// ✅ 仅在字段被触碰后显示错误
const { formState: { errors, touchedFields } } = useForm();

{touchedFields.email && errors.email && (
  <span className="error">{errors.email.message}</span>
)}

// 或者:使用mode="onBlur"在失去焦点时验证
const form = useForm({
  mode: 'onBlur' // 用户离开字段时验证
});
重要性: 更好的用户体验 → 用户在输入前不会被错误提示打扰

Anti-Pattern 4: No Async Validation

反模式4:异步验证缺失

Problem: Can't check username availability, validate addresses, etc.
Correct approach:
typescript
// ✅ Async validation with debounce
const schema = z.object({
  username: z.string().refine(
    async (username) => {
      // Debounced API call
      const available = await checkUsernameAvailability(username);
      return available;
    },
    { message: 'Username already taken' }
  )
});

// Or: Custom async validation in RHF
register('username', {
  validate: {
    checkAvailable: async (value) => {
      const response = await fetch(`/api/check-username?q=${value}`);
      return response.ok || 'Username taken';
    }
  }
});
Best practice: Debounce async validation to avoid API spam

问题: 无法检查用户名可用性、验证地址等
正确示例:
typescript
// ✅ 带防抖的异步验证
const schema = z.object({
  username: z.string().refine(
    async (username) => {
      // 防抖后的API调用
      const available = await checkUsernameAvailability(username);
      return available;
    },
    { message: 'Username already taken' }
  )
});

// 或者:在RHF中使用自定义异步验证
register('username', {
  validate: {
    checkAvailable: async (value) => {
      const response = await fetch(`/api/check-username?q=${value}`);
      return response.ok || 'Username taken';
    }
  }
});
最佳实践: 对异步验证添加防抖,避免频繁调用API

Anti-Pattern 5: No Loading States

反模式5:加载状态缺失

Problem: User doesn't know validation is happening
Correct approach:
typescript
// ✅ Show loading state during async validation
const { formState: { isValidating, isSubmitting } } = useForm();

<button disabled={isValidating || isSubmitting}>
  {isSubmitting ? 'Submitting...' :
   isValidating ? 'Checking...' :
   'Submit'}
</button>

问题: 用户不知道验证正在进行
正确示例:
typescript
// ✅ 异步验证时显示加载状态
const { formState: { isValidating, isSubmitting } } = useForm();

<button disabled={isValidating || isSubmitting}>
  {isSubmitting ? 'Submitting...' :
   isValidating ? 'Checking...' :
   'Submit'}
</button>

Implementation Patterns

实现模式

Pattern 1: Basic Form with Zod

模式1:基于Zod的基础表单

typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// Define schema
const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  rememberMe: z.boolean().optional()
});

type LoginForm = z.infer<typeof loginSchema>;

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }
  } = useForm<LoginForm>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      rememberMe: false
    }
  });

  const onSubmit = async (data: LoginForm) => {
    await api.login(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          {...register('email')}
          type="email"
          placeholder="Email"
        />
        {errors.email && <span className="error">{errors.email.message}</span>}
      </div>

      <div>
        <input
          {...register('password')}
          type="password"
          placeholder="Password"
        />
        {errors.password && <span className="error">{errors.password.message}</span>}
      </div>

      <div>
        <label>
          <input {...register('rememberMe')} type="checkbox" />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}
typescript
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

// 定义schema
const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
  rememberMe: z.boolean().optional()
});

type LoginForm = z.infer<typeof loginSchema>;

function LoginForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting }
  } = useForm<LoginForm>({
    resolver: zodResolver(loginSchema),
    defaultValues: {
      rememberMe: false
    }
  });

  const onSubmit = async (data: LoginForm) => {
    await api.login(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          {...register('email')}
          type="email"
          placeholder="Email"
        />
        {errors.email && <span className="error">{errors.email.message}</span>}
      </div>

      <div>
        <input
          {...register('password')}
          type="password"
          placeholder="Password"
        />
        {errors.password && <span className="error">{errors.password.message}</span>}
      </div>

      <div>
        <label>
          <input {...register('rememberMe')} type="checkbox" />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}

Pattern 2: Multi-Step Wizard

模式2:多步骤向导

typescript
const stepSchemas = [
  // Step 1: Personal Info
  z.object({
    firstName: z.string().min(1, 'Required'),
    lastName: z.string().min(1, 'Required'),
    email: z.string().email()
  }),
  // Step 2: Address
  z.object({
    street: z.string().min(1, 'Required'),
    city: z.string().min(1, 'Required'),
    zipCode: z.string().regex(/^\d{5}$/, 'Invalid ZIP')
  }),
  // Step 3: Payment
  z.object({
    cardNumber: z.string().regex(/^\d{16}$/, 'Invalid card'),
    expiry: z.string().regex(/^\d{2}\/\d{2}$/, 'MM/YY format'),
    cvv: z.string().regex(/^\d{3}$/, '3 digits')
  })
];

function MultiStepForm() {
  const [step, setStep] = useState(0);
  const [formData, setFormData] = useState({});

  const form = useForm({
    resolver: zodResolver(stepSchemas[step])
  });

  const nextStep = async () => {
    const isValid = await form.trigger(); // Validate current step

    if (isValid) {
      setFormData({ ...formData, ...form.getValues() });
      setStep(step + 1);
    }
  };

  const prevStep = () => {
    setFormData({ ...formData, ...form.getValues() });
    setStep(step - 1);
  };

  const onSubmit = async (data) => {
    const finalData = { ...formData, ...data };
    await api.submitApplication(finalData);
  };

  return (
    <div>
      <progress value={step + 1} max={stepSchemas.length} />

      <form onSubmit={form.handleSubmit(step === 2 ? onSubmit : nextStep)}>
        {step === 0 && <PersonalInfoStep register={form.register} errors={form.formState.errors} />}
        {step === 1 && <AddressStep register={form.register} errors={form.formState.errors} />}
        {step === 2 && <PaymentStep register={form.register} errors={form.formState.errors} />}

        <div>
          {step > 0 && <button type="button" onClick={prevStep}>Back</button>}
          <button type="submit">
            {step === 2 ? 'Submit' : 'Next'}
          </button>
        </div>
      </form>
    </div>
  );
}
typescript
const stepSchemas = [
  // 步骤1:个人信息
  z.object({
    firstName: z.string().min(1, 'Required'),
    lastName: z.string().min(1, 'Required'),
    email: z.string().email()
  }),
  // 步骤2:地址信息
  z.object({
    street: z.string().min(1, 'Required'),
    city: z.string().min(1, 'Required'),
    zipCode: z.string().regex(/^\d{5}$/, 'Invalid ZIP')
  }),
  // 步骤3:支付信息
  z.object({
    cardNumber: z.string().regex(/^\d{16}$/, 'Invalid card'),
    expiry: z.string().regex(/^\d{2}\/\d{2}$/, 'MM/YY format'),
    cvv: z.string().regex(/^\d{3}$/, '3 digits')
  })
];

function MultiStepForm() {
  const [step, setStep] = useState(0);
  const [formData, setFormData] = useState({});

  const form = useForm({
    resolver: zodResolver(stepSchemas[step])
  });

  const nextStep = async () => {
    const isValid = await form.trigger(); // 验证当前步骤

    if (isValid) {
      setFormData({ ...formData, ...form.getValues() });
      setStep(step + 1);
    }
  };

  const prevStep = () => {
    setFormData({ ...formData, ...form.getValues() });
    setStep(step - 1);
  };

  const onSubmit = async (data) => {
    const finalData = { ...formData, ...data };
    await api.submitApplication(finalData);
  };

  return (
    <div>
      <progress value={step + 1} max={stepSchemas.length} />

      <form onSubmit={form.handleSubmit(step === 2 ? onSubmit : nextStep)}>
        {step === 0 && <PersonalInfoStep register={form.register} errors={form.formState.errors} />}
        {step === 1 && <AddressStep register={form.register} errors={form.formState.errors} />}
        {step === 2 && <PaymentStep register={form.register} errors={form.formState.errors} />}

        <div>
          {step > 0 && <button type="button" onClick={prevStep}>Back</button>}
          <button type="submit">
            {step === 2 ? 'Submit' : 'Next'}
          </button>
        </div>
      </form>
    </div>
  );
}

Pattern 3: Dynamic Field Arrays

模式3:动态字段数组

typescript
const schema = z.object({
  items: z.array(z.object({
    name: z.string().min(1, 'Required'),
    quantity: z.number().min(1, 'At least 1'),
    price: z.number().min(0, 'Must be positive')
  })).min(1, 'Add at least one item')
});

function OrderForm() {
  const { register, control, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
    defaultValues: {
      items: [{ name: '', quantity: 1, price: 0 }]
    }
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'items'
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input
            {...register(`items.${index}.name`)}
            placeholder="Item name"
          />
          <input
            {...register(`items.${index}.quantity`, { valueAsNumber: true })}
            type="number"
          />
          <input
            {...register(`items.${index}.price`, { valueAsNumber: true })}
            type="number"
            step="0.01"
          />
          <button type="button" onClick={() => remove(index)}>
            Remove
          </button>
        </div>
      ))}

      <button type="button" onClick={() => append({ name: '', quantity: 1, price: 0 })}>
        Add Item
      </button>

      <button type="submit">Submit Order</button>
    </form>
  );
}
typescript
const schema = z.object({
  items: z.array(z.object({
    name: z.string().min(1, 'Required'),
    quantity: z.number().min(1, 'At least 1'),
    price: z.number().min(0, 'Must be positive')
  })).min(1, 'Add at least one item')
});

function OrderForm() {
  const { register, control, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
    defaultValues: {
      items: [{ name: '', quantity: 1, price: 0 }]
    }
  });

  const { fields, append, remove } = useFieldArray({
    control,
    name: 'items'
  });

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input
            {...register(`items.${index}.name`)}
            placeholder="Item name"
          />
          <input
            {...register(`items.${index}.quantity`, { valueAsNumber: true })}
            type="number"
          />
          <input
            {...register(`items.${index}.price`, { valueAsNumber: true })}
            type="number"
            step="0.01"
          />
          <button type="button" onClick={() => remove(index)}>
            Remove
          </button>
        </div>
      ))}

      <button type="button" onClick={() => append({ name: '', quantity: 1, price: 0 })}>
        Add Item
      </button>

      <button type="submit">Submit Order</button>
    </form>
  );
}

Pattern 4: Autosave (Debounced)

模式4:自动保存(带防抖)

typescript
import { useDebounce } from 'use-debounce';
import { useEffect } from 'react';

function AutosaveForm() {
  const { watch, register } = useForm();
  const formValues = watch(); // Watch all fields

  // Debounce to avoid saving on every keystroke
  const [debouncedValues] = useDebounce(formValues, 1000);

  useEffect(() => {
    // Save to localStorage or API
    localStorage.setItem('draft', JSON.stringify(debouncedValues));
    // Or: await api.saveDraft(debouncedValues);
  }, [debouncedValues]);

  return (
    <form>
      <input {...register('title')} placeholder="Title" />
      <textarea {...register('content')} placeholder="Content" />
      <small>Autosaved</small>
    </form>
  );
}

typescript
import { useDebounce } from 'use-debounce';
import { useEffect } from 'react';

function AutosaveForm() {
  const { watch, register } = useForm();
  const formValues = watch(); // 监听所有字段

  // 防抖避免每次按键都保存
  const [debouncedValues] = useDebounce(formValues, 1000);

  useEffect(() => {
    // 保存到localStorage或API
    localStorage.setItem('draft', JSON.stringify(debouncedValues));
    // 或者:await api.saveDraft(debouncedValues);
  }, [debouncedValues]);

  return (
    <form>
      <input {...register('title')} placeholder="Title" />
      <textarea {...register('content')} placeholder="Content" />
      <small>Autosaved</small>
    </form>
  );
}

Form UX Best Practices

表单用户体验最佳实践

1. Validate on Blur (Not on Change)

1. 失去焦点时验证(而非输入时)

typescript
const form = useForm({
  mode: 'onBlur' // Validate when user leaves field
  // NOT 'onChange' - too aggressive
});
typescript
const form = useForm({
  mode: 'onBlur' // 用户离开字段时验证
  // 不要用'onChange' - 过于激进
});

2. Disable Submit While Invalid

2. 表单无效时禁用提交按钮

typescript
<button
  type="submit"
  disabled={!form.formState.isValid || form.formState.isSubmitting}
>
  Submit
</button>
typescript
<button
  type="submit"
  disabled={!form.formState.isValid || form.formState.isSubmitting}
>
  Submit
</button>

3. Focus First Error on Submit

3. 提交时聚焦第一个错误字段

typescript
const onSubmit = async (data) => {
  try {
    await api.submit(data);
  } catch (error) {
    // Focus first error field
    const firstError = Object.keys(errors)[0];
    form.setFocus(firstError);
  }
};
typescript
const onSubmit = async (data) => {
  try {
    await api.submit(data);
  } catch (error) {
    // 聚焦第一个错误字段
    const firstError = Object.keys(errors)[0];
    form.setFocus(firstError);
  }
};

4. Optimistic UI Updates

4. 乐观UI更新

typescript
const onSubmit = async (data) => {
  // Optimistically update UI
  setItems([...items, data]);

  try {
    await api.createItem(data);
  } catch (error) {
    // Rollback on error
    setItems(items);
    toast.error('Failed to save');
  }
};

typescript
const onSubmit = async (data) => {
  // 乐观更新UI
  setItems([...items, data]);

  try {
    await api.createItem(data);
  } catch (error) {
    // 错误时回滚
    setItems(items);
    toast.error('Failed to save');
  }
};

Production Checklist

生产环境检查清单

□ Zod schemas for all forms
□ Type inference used (z.infer<typeof schema>)
□ Validation mode set appropriately (onBlur/onSubmit)
□ Error messages clear and actionable
□ Loading states for async operations
□ Focus management on errors
□ Autosave for long forms
□ Form state persisted (localStorage/session)
□ File upload progress indicators
□ Keyboard navigation tested
□ Accessibility (ARIA labels, error announcements)
□ Mobile-friendly (large touch targets)

□ 所有表单使用Zod schema
□ 使用类型推断(z.infer<typeof schema>)
□ 验证模式设置合理(onBlur/onSubmit)
□ 错误提示清晰且可操作
□ 异步操作有加载状态
□ 错误发生时聚焦对应字段
□ 长表单支持自动保存
□ 表单状态持久化(localStorage/session)
□ 文件上传有进度指示器
□ 键盘导航已测试
□ 可访问性(ARIA标签、错误通知)
□ 移动端友好(大触摸目标)

When to Use vs Avoid

适用与不适用场景对比

ScenarioUse This Skill?
User registration with validation✅ Yes
Multi-step checkout flow✅ Yes
Dynamic form builder✅ Yes
Simple newsletter signup❌ No - use native HTML
Backend-only validation❌ No - use Joi/Yup on server
Non-React framework❌ No - use framework-specific solution

场景是否适用本方案
带验证的用户注册✅ 是
多步骤结账流程✅ 是
动态表单构建器✅ 是
简单新闻订阅❌ 否 - 使用原生HTML
仅后端验证❌ 否 - 在服务端使用Joi/Yup
非React框架❌ 否 - 使用框架专属方案

Technology Comparison

技术对比

FeatureRHF + ZodFormik + YupNative HTML5
Performance⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Type Safety⭐⭐⭐⭐⭐⭐⭐⭐
Bundle Size8KB30KB0KB
DevEx⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Field Arrays⭐⭐⭐⭐⭐⭐⭐⭐⭐
Async Validation⭐⭐⭐⭐⭐⭐⭐⭐

特性RHF + ZodFormik + Yup原生HTML5
性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
类型安全⭐⭐⭐⭐⭐⭐⭐⭐
包体积8KB30KB0KB
开发体验⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
字段数组⭐⭐⭐⭐⭐⭐⭐⭐⭐
异步验证⭐⭐⭐⭐⭐⭐⭐⭐

References

参考资料

  • /references/zod-patterns.md
    - Advanced Zod schema patterns
  • /references/accessibility.md
    - Form accessibility guidelines
  • /references/file-upload.md
    - File upload with progress tracking
  • /references/zod-patterns.md
    - 高级Zod schema模式
  • /references/accessibility.md
    - 表单可访问性指南
  • /references/file-upload.md
    - 带进度追踪的文件上传

Scripts

脚本

  • scripts/generate_form.ts
    - Generate form from Zod schema
  • scripts/validate_schemas.ts
    - Lint Zod schemas for common issues
  • scripts/generate_form.ts
    - 从Zod schema生成表单
  • scripts/validate_schemas.ts
    - 检查Zod schema的常见问题

Assets

资源

  • assets/form-templates/
    - Ready-to-use form components

This skill guides: Form validation architecture | react-hook-form patterns | Zod schema design | Multi-step wizards | Field arrays | Autosave | Async validation
  • assets/form-templates/
    - 可直接使用的表单组件

本技能涵盖: 表单验证架构 | react-hook-form模式 | Zod schema设计 | 多步骤向导 | 字段数组 | 自动保存 | 异步验证