web-forms-tanstack-form

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TanStack Form Patterns

TanStack Form 模式

Quick Guide: Use
useForm
with
defaultValues
and typed generics. Render fields with
form.Field
using the render-prop
children
pattern. Validation lives in the
validators
prop on both form and field level — use
onChange
,
onBlur
,
onSubmit
(sync) and their
Async
variants. Use
mode="array"
for dynamic field lists with
pushValue
/
removeValue
. Use
onChangeListenTo
for cross-field validation. For app-wide consistency, create a shared
useAppForm
via
createFormHook
. Always provide
defaultValues
— TanStack Form infers types from them.

<critical_requirements>
快速指南: 将
useForm
与
defaultValues
和类型化泛型配合使用。通过
form.Field
采用渲染属性
children
模式渲染字段。验证逻辑位于表单和字段级别的
validators
属性中——使用
onChange
、
onBlur
、
onSubmit
(同步)及其
Async
变体。对动态字段列表使用
mode="array"
,搭配
pushValue
/
removeValue
。使用
onChangeListenTo
实现跨字段验证。为了保持应用全局一致性,通过
createFormHook
创建共享的
useAppForm
。务必提供
defaultValues
——TanStack Form 会从中推断类型。

<critical_requirements>

CRITICAL: Before Using This Skill

重要提示:使用此技能前须知

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST provide
defaultValues
to
useForm
— TanStack Form infers field types from them)
(You MUST use
form.Field
with the
children
render prop — TanStack Form does not use
register
or
Controller
)
(You MUST use the
validators
prop for validation — NOT inline
rules
or external resolver wrappers)
(You MUST handle
field.state.meta.errors
as an array — always
.map()
over errors)
(You MUST call
form.handleSubmit()
inside the form's
onSubmit
handler with
e.preventDefault()
)
</critical_requirements>

Auto-detection: TanStack Form, @tanstack/react-form, @tanstack/vue-form, @tanstack/solid-form, @tanstack/angular-form, @tanstack/lit-form, useForm from tanstack, form.Field, createFormHook, createFormHookContexts, useAppForm, fieldContext, formContext, handleSubmit tanstack, pushValue, removeValue, onChangeListenTo, field.handleChange, field.handleBlur, field.state, formDevtoolsPlugin
When to use:
  • Building type-safe forms where field types are inferred from
    defaultValues
  • Managing complex validation with sync, async, and cross-field rules
  • Dynamic forms with add/remove field groups (array fields)
  • Multi-framework projects (React, Vue, Solid, Angular, Lit)
  • Projects already using the TanStack ecosystem
When NOT to use:
  • Single input without validation (use native state)
  • Server-only forms with server actions (use native form + action)
  • Read-only data display (not a form scenario)

所有代码必须遵循 CLAUDE.md 中的项目约定(短横线命名、命名导出、导入排序、
import type
、命名常量)
(必须为
useForm
提供
defaultValues
——TanStack Form 会从中推断字段类型)
(必须使用带
children
渲染属性的
form.Field
——TanStack Form 不使用
register
或
Controller
)
(必须使用
validators
属性进行验证——不能使用内联
rules
或外部解析器包装器)
(必须将
field.state.meta.errors
作为数组处理——始终通过
.map()
遍历错误)
(必须在表单的
onSubmit
处理函数中调用
form.handleSubmit()
,并搭配
e.preventDefault()
)
</critical_requirements>

自动检测: TanStack Form、@tanstack/react-form、@tanstack/vue-form、@tanstack/solid-form、@tanstack/angular-form、@tanstack/lit-form、tanstack 中的 useForm、form.Field、createFormHook、createFormHookContexts、useAppForm、fieldContext、formContext、tanstack 的 handleSubmit、pushValue、removeValue、onChangeListenTo、field.handleChange、field.handleBlur、field.state、formDevtoolsPlugin
适用场景:
  • 构建字段类型从
    defaultValues
    推断的类型安全表单
  • 管理包含同步、异步和跨字段规则的复杂验证
  • 包含添加/删除字段组的动态表单(数组字段)
  • 多框架项目(React、Vue、Solid、Angular、Lit)
  • 已使用 TanStack 生态的项目
不适用场景:
  • 无验证的单个输入(使用原生状态)
  • 带服务端动作的仅服务端表单(使用原生表单 + 动作)
  • 只读数据展示(非表单场景)

Table of Contents

目录

Detailed Resources:
  • examples/core.md - Basic form, Field component, TypeScript, form submission
  • examples/validation.md - Sync/async validation, validator adapters, form-level validation
  • examples/arrays.md - Dynamic array fields with pushValue/removeValue
  • examples/composition.md - createFormHook, useAppForm, listeners, side effects
  • reference.md - API tables, validator events, decision frameworks

<philosophy>
详细资源:
  • examples/core.md - 基础表单、Field 组件、TypeScript、表单提交
  • examples/validation.md - 同步/异步验证、验证器适配器、表单级验证
  • examples/arrays.md - 搭配 pushValue/removeValue 的动态数组字段
  • examples/composition.md - createFormHook、useAppForm、监听器、副作用
  • reference.md - API 表格、验证器事件、决策框架

<philosophy>

Philosophy

设计理念

TanStack Form is headless and type-safe by design. It owns zero UI — you render every input yourself. The library provides form state, validation orchestration, and field management. Types flow from
defaultValues
through every field name, value, and error — no manual generics required (though you can provide them).
Core Principles:
  1. Type inference from defaults -
    defaultValues
    defines the form shape; field names and values are fully typed
  2. Headless - Zero UI opinions; works with any component library or native inputs
  3. Validation-event-driven - Validators attach to specific events (
    onChange
    ,
    onBlur
    ,
    onSubmit
    ) per field or per form
  4. Framework-agnostic core - Same mental model across React, Vue, Solid, Angular, and Lit
  5. Composition via factory -
    createFormHook
    shares field/form components across an app
</philosophy>
<patterns>
TanStack Form 本质是无头式且类型安全的。它不包含任何 UI——所有输入都由你自行渲染。该库提供表单状态、验证编排和字段管理功能。类型从
defaultValues
贯穿每个字段名称、值和错误——无需手动定义泛型(不过你也可以提供)。
核心原则:
  1. 从默认值推断类型 -
    defaultValues
    定义表单结构;字段名称和值完全类型化
  2. 无头式 - 无 UI 偏见;可与任何组件库或原生输入配合使用
  3. 验证事件驱动 - 验证器附加到字段或表单的特定事件(
    onChange
    、
    onBlur
    、
    onSubmit
    )
  4. 框架无关核心 - 在 React、Vue、Solid、Angular 和 Lit 中遵循相同的思维模型
  5. 通过工厂模式组合 -
    createFormHook
    在应用中共享字段/表单组件
</philosophy>
<patterns>

Core Patterns

核心模式

Pattern 1: Basic useForm + form.Field

模式 1:基础 useForm + form.Field

Every form starts with
useForm
and renders fields via
form.Field
. The
children
render prop receives the field API with
state
,
handleChange
, and
handleBlur
.
tsx
import { useForm } from "@tanstack/react-form";

const form = useForm({
  defaultValues: { name: "", email: "" },
  onSubmit: async ({ value }) => {
    await submitToApi(value);
  },
});

return (
  <form
    onSubmit={(e) => {
      e.preventDefault();
      form.handleSubmit();
    }}
  >
    <form.Field
      name="email"
      children={(field) => (
        <input
          value={field.state.value}
          onBlur={field.handleBlur}
          onChange={(e) => field.handleChange(e.target.value)}
        />
      )}
    />
  </form>
);
Key difference from other form libraries: No
register
, no
Controller
, no
ref
forwarding. You always use
field.handleChange
and
field.state.value
explicitly.
See examples/core.md for complete form with error display and accessibility.

每个表单都从
useForm
开始,并通过
form.Field
渲染字段。
children
渲染属性会接收包含
state
、
handleChange
和
handleBlur
的字段 API。
tsx
import { useForm } from "@tanstack/react-form";

const form = useForm({
  defaultValues: { name: "", email: "" },
  onSubmit: async ({ value }) => {
    await submitToApi(value);
  },
});

return (
  <form
    onSubmit={(e) => {
      e.preventDefault();
      form.handleSubmit();
    }}
  >
    <form.Field
      name="email"
      children={(field) => (
        <input
          value={field.state.value}
          onBlur={field.handleBlur}
          onChange={(e) => field.handleChange(e.target.value)}
        />
      )}
    />
  </form>
);
与其他表单库的关键区别: 没有
register
,没有
Controller
,没有
ref
转发。你始终需要显式使用
field.handleChange
和
field.state.value
。
查看 examples/core.md 获取包含错误展示和无障碍支持的完整表单示例。

Pattern 2: Field-Level Validation

模式 2:字段级验证

Validators are functions on the
validators
prop. Sync validators return a string (error) or
undefined
(valid). Async validators use
onChangeAsync
,
onBlurAsync
,
onSubmitAsync
.
tsx
<form.Field
  name="age"
  validators={{
    onChange: ({ value }) => (value < 13 ? "Must be 13 or older" : undefined),
    onBlurAsync: async ({ value }) => {
      const exists = await checkAge(value);
      return exists ? undefined : "Age not valid on server";
    },
  }}
  children={(field) => (
    <div>
      <input
        type="number"
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.valueAsNumber)}
      />
      {field.state.meta.errors.map((err) => (
        <em key={err} role="alert">
          {err}
        </em>
      ))}
    </div>
  )}
/>
Sync-first gating: When both
onBlur
and
onBlurAsync
exist, the async validator only runs if the sync validator passes. Same for
onChange
/
onChangeAsync
.
See examples/validation.md for all validation patterns and adapter integration.

验证器是
validators
属性上的函数。同步验证器返回字符串(错误信息)或
undefined
(验证通过)。异步验证器使用
onChangeAsync
、
onBlurAsync
、
onSubmitAsync
。
tsx
<form.Field
  name="age"
  validators={{
    onChange: ({ value }) => (value < 13 ? "必须年满 13 岁" : undefined),
    onBlurAsync: async ({ value }) => {
      const exists = await checkAge(value);
      return exists ? undefined : "该年龄在服务端无效";
    },
  }}
  children={(field) => (
    <div>
      <input
        type="number"
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.valueAsNumber)}
      />
      {field.state.meta.errors.map((err) => (
        <em key={err} role="alert">
          {err}
        </em>
      ))}
    </div>
  )}
/>
同步优先机制: 当同时存在
onBlur
和
onBlurAsync
时,只有同步验证器通过后,异步验证器才会运行。
onChange
/
onChangeAsync
同理。
查看 examples/validation.md 获取所有验证模式和适配器集成示例。

Pattern 3: Linked Fields (Cross-Field Validation)

模式 3:关联字段(跨字段验证)

Use
onChangeListenTo
to re-run a field's validator when another field changes. This solves the stale-validation problem (e.g., confirm password).
tsx
<form.Field
  name="confirm_password"
  validators={{
    onChangeListenTo: ["password"],
    onChange: ({ value, fieldApi }) => {
      if (value !== fieldApi.form.getFieldValue("password")) {
        return "Passwords do not match";
      }
      return undefined;
    },
  }}
  children={(field) => (/* ... */)}
/>
Why this matters: Without
onChangeListenTo
, changing the
password
field does not re-validate
confirm_password
. The error stays stale until the user interacts with the confirm field again.
See examples/validation.md Pattern 4 for a complete linked fields example.

使用
onChangeListenTo
在其他字段变化时重新运行当前字段的验证器。这解决了验证过时的问题(例如确认密码)。
tsx
<form.Field
  name="confirm_password"
  validators={{
    onChangeListenTo: ["password"],
    onChange: ({ value, fieldApi }) => {
      if (value !== fieldApi.form.getFieldValue("password")) {
        return "密码不匹配";
      }
      return undefined;
    },
  }}
  children={(field) => (/* ... */)}
/>
重要性: 如果没有
onChangeListenTo
,修改
password
字段不会重新验证
confirm_password
,错误信息会一直保持过时状态,直到用户再次与确认字段交互。
查看 examples/validation.md 模式 4 获取完整的关联字段示例。

Pattern 4: Array Fields

模式 4:数组字段

Use
mode="array"
on
form.Field
to get
pushValue
,
removeValue
,
swapValues
,
moveValue
, and
insertValue
for dynamic field groups.
tsx
<form.Field
  name="hobbies"
  mode="array"
  children={(hobbiesField) => (
    <div>
      {hobbiesField.state.value.map((_, i) => (
        <div key={i}>
          <form.Field
            name={`hobbies[${i}].name`}
            children={(field) => (
              <input
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
              />
            )}
          />
          <button type="button" onClick={() => hobbiesField.removeValue(i)}>
            Remove
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() => hobbiesField.pushValue({ name: "" })}
      >
        Add hobby
      </button>
    </div>
  )}
/>
Important:
pushValue
requires a complete object matching the array item shape. Partial objects will cause type errors.
See examples/arrays.md for a complete dynamic list form.

在
form.Field
上使用
mode="array"
,以获取
pushValue
、
removeValue
、
swapValues
、
moveValue
和
insertValue
来处理动态字段组。
tsx
<form.Field
  name="hobbies"
  mode="array"
  children={(hobbiesField) => (
    <div>
      {hobbiesField.state.value.map((_, i) => (
        <div key={i}>
          <form.Field
            name={`hobbies[${i}].name`}
            children={(field) => (
              <input
                value={field.state.value}
                onChange={(e) => field.handleChange(e.target.value)}
              />
            )}
          />
          <button type="button" onClick={() => hobbiesField.removeValue(i)}>
            删除
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() => hobbiesField.pushValue({ name: "" })}
      >
        添加爱好
      </button>
    </div>
  )}
/>
注意:
pushValue
需要传入与数组项结构完全匹配的完整对象。部分对象会导致类型错误。
查看 examples/arrays.md 获取完整的动态列表表单示例。

Pattern 5: Form-Level Validation

模式 5:表单级验证

Validators on
useForm
apply to the entire form. Use
onSubmitAsync
for server-side validation that returns field-specific errors.
tsx
const form = useForm({
  defaultValues: { username: "", age: 0 },
  validators: {
    onSubmitAsync: async ({ value }) => {
      const errors = await validateOnServer(value);
      if (errors) {
        return {
          form: "Submission failed",
          fields: {
            username: errors.username,
            age: errors.age,
          },
        };
      }
      return null;
    },
  },
});
Return shape:
{ form?: string, fields: Record<string, string> }
— the
form
key is optional for form-level errors,
fields
maps field names to their error messages. Return
null
when valid.
See examples/validation.md Pattern 3 for complete form-level validation.

useForm
上的验证器适用于整个表单。使用
onSubmitAsync
实现返回字段特定错误的服务端验证。
tsx
const form = useForm({
  defaultValues: { username: "", age: 0 },
  validators: {
    onSubmitAsync: async ({ value }) => {
      const errors = await validateOnServer(value);
      if (errors) {
        return {
          form: "提交失败",
          fields: {
            username: errors.username,
            age: errors.age,
          },
        };
      }
      return null;
    },
  },
});
返回结构:
{ form?: string, fields: Record<string, string> }
——
form
键是可选的表单级错误,
fields
映射字段名称到对应的错误信息。验证通过时返回
null
。
查看 examples/validation.md 模式 3 获取完整的表单级验证示例。

Pattern 6: createFormHook (App-Wide Composition)

模式 6:createFormHook(应用全局组合)

Use
createFormHook
to share custom field components and form components across the app. This eliminates boilerplate and enforces consistency.
tsx
import { createFormHookContexts, createFormHook } from "@tanstack/react-form";

export const { fieldContext, formContext, useFieldContext } =
  createFormHookContexts();

export const { useAppForm, withForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: {
    TextField: TextFieldComponent,
    SelectField: SelectFieldComponent,
  },
  formComponents: {
    SubmitButton: SubmitButtonComponent,
  },
});
Usage:
useAppForm
accepts all
useForm
options. Registered
fieldComponents
and
formComponents
are available on the returned form instance:
form.AppField
for custom field components,
form.AppForm
for form-level components.
See examples/composition.md for the full factory setup and custom component patterns.

使用
createFormHook
在应用中共享自定义字段组件和表单组件。这能消除重复代码并确保一致性。
tsx
import { createFormHookContexts, createFormHook } from "@tanstack/react-form";

export const { fieldContext, formContext, useFieldContext } =
  createFormHookContexts();

export const { useAppForm, withForm } = createFormHook({
  fieldContext,
  formContext,
  fieldComponents: {
    TextField: TextFieldComponent,
    SelectField: SelectFieldComponent,
  },
  formComponents: {
    SubmitButton: SubmitButtonComponent,
  },
});
用法:
useAppForm
接受所有
useForm
的配置项。已注册的
fieldComponents
和
formComponents
可在返回的表单实例上使用:
form.AppField
用于自定义字段组件,
form.AppForm
用于表单级组件。
查看 examples/composition.md 获取完整的工厂设置和自定义组件模式。

Pattern 7: Listeners (Side Effects)

模式 7:监听器(副作用)

Listeners react to field events and perform side effects like resetting related fields. Use the
listeners
prop on
form.Field
.
tsx
<form.Field
  name="country"
  listeners={{
    onChange: ({ value }) => {
      form.setFieldValue("province", "");
    },
  }}
  children={(field) => (/* ... */)}
/>
Available events:
onChange
,
onBlur
,
onMount
,
onSubmit
. Listeners are for side effects only — they do not return validation errors.
See examples/composition.md Pattern 3 for a complete country/province cascade.

监听器响应字段事件并执行副作用,例如重置相关字段。使用
form.Field
上的
listeners
属性。
tsx
<form.Field
  name="country"
  listeners={{
    onChange: ({ value }) => {
      form.setFieldValue("province", "");
    },
  }}
  children={(field) => (/* ... */)}
/>
可用事件:
onChange
、
onBlur
、
onMount
、
onSubmit
。监听器仅用于执行副作用——不返回验证错误。
查看 examples/composition.md 模式 3 获取完整的国家/省份级联示例。

Pattern 8: form.Subscribe for Reactive UI

模式 8:form.Subscribe 用于响应式 UI

Use
form.Subscribe
to reactively render UI based on form state without re-rendering the entire form. Takes a
selector
to pick specific state.
tsx
<form.Subscribe
  selector={(state) => [state.canSubmit, state.isSubmitting]}
  children={([canSubmit, isSubmitting]) => (
    <button type="submit" disabled={!canSubmit || isSubmitting}>
      {isSubmitting ? "Submitting..." : "Submit"}
    </button>
  )}
/>
Why this matters: Without
form.Subscribe
, reading
form.state
directly causes the parent component to re-render on every state change. The selector narrows the subscription.
</patterns>
<red_flags>
使用
form.Subscribe
根据表单状态响应式渲染 UI,无需重新渲染整个表单。通过
selector
选择特定状态。
tsx
<form.Subscribe
  selector={(state) => [state.canSubmit, state.isSubmitting]}
  children={([canSubmit, isSubmitting]) => (
    <button type="submit" disabled={!canSubmit || isSubmitting}>
      {isSubmitting ? "提交中..." : "提交"}
    </button>
  )}
/>
重要性: 如果没有
form.Subscribe
,直接读取
form.state
会导致父组件在每次状态变化时重新渲染。选择器能缩小订阅范围。
</patterns>
<red_flags>

RED FLAGS

注意事项

High Priority Issues:
  • Using
    register
    or
    Controller
    patterns — TanStack Form uses
    form.Field
    with
    children
    render prop, not register/Controller
  • Missing
    defaultValues
    in
    useForm
    — types cannot be inferred, fields start as
    undefined
  • Calling
    form.handleSubmit()
    without
    e.preventDefault()
    — causes page reload
  • Reading
    form.state
    directly in the component body — causes full re-render on every change; use
    form.Subscribe
    or
    useStore
Medium Priority Issues:
  • Using
    onChange
    validator for expensive checks — use
    onChangeAsync
    with debounce or
    onBlurAsync
    instead
  • Providing partial objects to
    pushValue
    in array fields — must provide complete objects matching the array item type
  • Not using
    onChangeListenTo
    for cross-field validation — related field errors go stale
  • Wrapping
    form.handleSubmit()
    in another async function without error handling —
    handleSubmit
    does not catch errors thrown in
    onSubmit
Gotchas & Edge Cases:
  • field.state.meta.errors
    is always an array — never compare with
    ===
    , always
    .map()
    or
    .length
  • Sync validators gate async validators — if
    onChange
    fails,
    onChangeAsync
    does not run
  • Form-level
    onSubmitAsync
    validator returns
    { fields: { fieldName: "error" } }
    — not the same shape as field-level validators
  • field.state.meta.isTouched
    only becomes
    true
    after
    handleBlur
    fires — not on first
    handleChange
  • Array field access uses bracket notation:
    name={
    items[${i}].name
    }
    — not dot notation like
    items.${i}.name
  • form.Subscribe
    uses a
    selector
    prop to pick state — passing no selector subscribes to everything
  • createFormHook
    components are available as
    form.AppField
    and
    form.AppForm
    — not on
    form.Field
</red_flags>

<critical_reminders>
高优先级问题:
  • 使用
    register
    或
    Controller
    模式——TanStack Form 使用带
    children
    渲染属性的
    form.Field
    ,而非 register/Controller
  • useForm
    中缺少
    defaultValues
    ——无法推断类型,字段初始值为
    undefined
  • 调用
    form.handleSubmit()
    时未使用
    e.preventDefault()
    ——会导致页面刷新
  • 在组件主体中直接读取
    form.state
    ——每次变化都会导致完整重渲染;应使用
    form.Subscribe
    或
    useStore
中优先级问题:
  • 对耗时检查使用
    onChange
    验证器——应改用带防抖的
    onChangeAsync
    或
    onBlurAsync
  • 向数组字段的
    pushValue
    传入部分对象——必须传入与数组项类型完全匹配的完整对象
  • 跨字段验证未使用
    onChangeListenTo
    ——相关字段的错误会过时
  • 在未处理错误的情况下将
    form.handleSubmit()
    包装在另一个异步函数中——
    handleSubmit
    不会捕获
    onSubmit
    中抛出的错误
陷阱与边缘情况:
  • field.state.meta.errors
    始终是数组——永远不要用
    ===
    比较,应始终使用
    .map()
    或
    .length
  • 同步验证器会拦截异步验证器——如果
    onChange
    失败,
    onChangeAsync
    不会运行
  • 表单级
    onSubmitAsync
    验证器返回
    { fields: { fieldName: "error" } }
    ——与字段级验证器的返回结构不同
  • field.state.meta.isTouched
    仅在
    handleBlur
    触发后才变为
    true
    ——并非首次
    handleChange
    时
  • 数组字段访问使用方括号表示法:
    name={
    items[${i}].name
    }
    ——不能使用点表示法如
    items.${i}.name
  • form.Subscribe
    使用
    selector
    属性选择状态——不传入选择器会订阅所有状态
  • createFormHook
    组件通过
    form.AppField
    和
    form.AppForm
    访问——不在
    form.Field
    上
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

重要提醒

All code must follow project conventions in CLAUDE.md
(You MUST provide
defaultValues
to
useForm
— TanStack Form infers field types from them)
(You MUST use
form.Field
with the
children
render prop — TanStack Form does not use
register
or
Controller
)
(You MUST use the
validators
prop for validation — NOT inline
rules
or external resolver wrappers)
(You MUST handle
field.state.meta.errors
as an array — always
.map()
over errors)
(You MUST call
form.handleSubmit()
inside the form's
onSubmit
handler with
e.preventDefault()
)
Failure to follow these rules will break form state, lose type safety, and produce incorrect validation behavior.
</critical_reminders>
所有代码必须遵循 CLAUDE.md 中的项目约定
(必须为
useForm
提供
defaultValues
——TanStack Form 会从中推断字段类型)
(必须使用带
children
渲染属性的
form.Field
——TanStack Form 不使用
register
或
Controller
)
(必须使用
validators
属性进行验证——不能使用内联
rules
或外部解析器包装器)
(必须将
field.state.meta.errors
作为数组处理——始终通过
.map()
遍历错误)
(必须在表单的
onSubmit
处理函数中调用
form.handleSubmit()
,并搭配
e.preventDefault()
)
不遵循这些规则会破坏表单状态、丢失类型安全并导致验证行为异常。
</critical_reminders>