表单和字段验证

验证概念是 TanStack Form 功能的核心。TanStack Form 使验证高度可定制:

  • 您可以控制何时执行验证(onChange、onInput、onBlur、onSubmit...)
  • 验证规则可以在字段级别或表单级别定义
  • 验证可以是同步的或异步的(例如,作为 API 调用的结果)

何时执行验证?

这取决于您!<Field /> 组件接受一些回调作为属性,如 onChangeonBlur。这些回调会传递字段的当前值以及 fieldAPI 对象,以便您可以执行验证。如果您发现验证错误,只需将错误消息作为字符串返回,它将在 field.state.meta.errors 中可用。

这里是一个例子:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
                "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
                "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

在上面的例子中,验证在每次按键时执行(onChange)。如果我们希望在字段失去焦点时执行验证,我们会像这样更改上面的代码:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onBlur: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <!-- 我们总是需要实现 onChange,以便 TanStack Form 接收更改 -->
      <!-- 监听字段上的 onBlur 事件 -->
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @blur="field.handleBlur"
        @input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
                "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onBlur: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <!-- 我们总是需要实现 onChange,以便 TanStack Form 接收更改 -->
      <!-- 监听字段上的 onBlur 事件 -->
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @blur="field.handleBlur"
        @input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
                "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

因此,您可以通过实现所需的回调来控制何时执行验证。您甚至可以在不同时间执行不同的验证:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
      onBlur: ({ value }) => (value < 0 ? '无效值' : undefined),
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <!-- 我们总是需要实现 onChange,以便 TanStack Form 接收更改 -->
      <!-- 监听字段上的 onBlur 事件 -->
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @blur="field.handleBlur"
        @input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
                "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
      onBlur: ({ value }) => (value < 0 ? '无效值' : undefined),
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <!-- 我们总是需要实现 onChange,以便 TanStack Form 接收更改 -->
      <!-- 监听字段上的 onBlur 事件 -->
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @blur="field.handleBlur"
        @input="(e) => field.handleChange((e.target as HTMLInputElement).valueAsNumber)
                "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

在上面的例子中,我们在不同时间对同一字段验证不同的内容(在每次按键时和字段失去焦点时)。由于 field.state.meta.errors 是一个数组,所有在给定时间相关的错误都会显示。您还可以使用 field.state.meta.errorMap 根据验证完成的_时间_(onChange、onBlur 等)获取错误。有关显示错误的更多信息如下。

显示错误

一旦您设置了验证,您可以将错误从数组映射到您的 UI 中显示:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

或使用 errorMap 属性访问您要查找的特定错误:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
      <em role="alert" v-if="field.state.meta.errorMap['onChange']">{{
        field.state.meta.errorMap['onChange']
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) =>
        value < 13 ? '您必须年满 13 岁才能创建账户' : undefined,
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
      <em role="alert" v-if="field.state.meta.errorMap['onChange']">{{
        field.state.meta.errorMap['onChange']
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

值得一提的是,我们的 errors 数组和 errorMap 与验证器返回的类型匹配。这意味着:

vue
<form.Field
  name="age"
  :validators="{
    onChange: ({ value }) => (value < 13 ? { isOldEnough: false } : undefined),
  }"
>
  <template v-slot="{ field }">
      <!-- ... -->
      <!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
      <!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
      <em v-if="!field.state.meta.errorMap['onChange']?.isOldEnough">用户年龄不够</em>
  </template>
</form.Field>
<form.Field
  name="age"
  :validators="{
    onChange: ({ value }) => (value < 13 ? { isOldEnough: false } : undefined),
  }"
>
  <template v-slot="{ field }">
      <!-- ... -->
      <!-- errorMap.onChange is type `{isOldEnough: false} | undefined` -->
      <!-- meta.errors is type `Array<{isOldEnough: false} | undefined>` -->
      <em v-if="!field.state.meta.errorMap['onChange']?.isOldEnough">用户年龄不够</em>
  </template>
</form.Field>

字段级别与表单级别的验证

如上所示,每个 <Field> 通过 onChangeonBlur 等回调接受自己的验证规则。也可以通过向 useForm() 函数传递类似的回调来在表单级别(而不是逐个字段)定义验证规则。

示例:

vue
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: {
    age: 0,
  },
  onSubmit: async ({ value }) => {
    console.log(value)
  },
  validators: {
    // 像向字段添加验证器一样向表单添加验证器
    onChange({ value }) {
      if (value.age < 13) {
        return '必须年满 13 岁才能注册'
      }
      return undefined
    },
  },
})

// 订阅表单的错误映射,以便更新时会重新渲染
// 或者,您可以使用 `form.Subscribe`
const formErrorMap = form.useStore((state) => state.errorMap)
</script>

<template>
  <!-- ... -->
  <div v-if="formErrorMap.onChange">
    <em role="alert">
      表单出现错误:{{ formErrorMap.onChange }}
    </em>
  </div>
  <!-- ... -->
</template>
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: {
    age: 0,
  },
  onSubmit: async ({ value }) => {
    console.log(value)
  },
  validators: {
    // 像向字段添加验证器一样向表单添加验证器
    onChange({ value }) {
      if (value.age < 13) {
        return '必须年满 13 岁才能注册'
      }
      return undefined
    },
  },
})

// 订阅表单的错误映射,以便更新时会重新渲染
// 或者,您可以使用 `form.Subscribe`
const formErrorMap = form.useStore((state) => state.errorMap)
</script>

<template>
  <!-- ... -->
  <div v-if="formErrorMap.onChange">
    <em role="alert">
      表单出现错误:{{ formErrorMap.onChange }}
    </em>
  </div>
  <!-- ... -->
</template>

从表单验证器设置字段级别错误

您可以从表单的验证器在字段上设置错误。一个常见的用例是通过在表单的 onSubmitAsync 验证器中调用单个 API 端点来在提交时验证所有字段。

vue
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: {
    age: 0,
    socials: [],
    details: {
      email: '',
    },
  },
  validators: {
    // 像向字段添加验证器一样向表单添加验证器
    onSubmitAsync({ value }) {
      // 在服务器上验证值
      const hasErrors = await verifyDataOnServer(value)
      if (hasErrors) {
        return {
          form: '无效数据', // `form` 键是可选的
          fields: {
            age: '必须年满 13 岁才能注册',
            // 使用字段名称在嵌套字段上设置错误
            'socials[0].url': '提供的 URL 不存在',
            'details.email': '需要电子邮件',
          },
        }
      }

      return null
    },
  },
})
</script>

<template>
  <!-- ... -->
</template>
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: {
    age: 0,
    socials: [],
    details: {
      email: '',
    },
  },
  validators: {
    // 像向字段添加验证器一样向表单添加验证器
    onSubmitAsync({ value }) {
      // 在服务器上验证值
      const hasErrors = await verifyDataOnServer(value)
      if (hasErrors) {
        return {
          form: '无效数据', // `form` 键是可选的
          fields: {
            age: '必须年满 13 岁才能注册',
            // 使用字段名称在嵌套字段上设置错误
            'socials[0].url': '提供的 URL 不存在',
            'details.email': '需要电子邮件',
          },
        }
      }

      return null
    },
  },
})
</script>

<template>
  <!-- ... -->
</template>

值得一提的是,如果您有一个返回错误的表单验证函数,该错误可能会被字段特定的验证覆盖。

这意味着:

vue
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: {
    age: 0,
  },
  validators: {
    onChange: ({ value }) => {
      return {
        fields: {
          age: value.age < 12 ? '太年轻了!' : undefined,
        },
      }
    },
  },
})
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) => (value % 2 === 0 ? '必须是奇数!' : undefined),
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
</template>
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: {
    age: 0,
  },
  validators: {
    onChange: ({ value }) => {
      return {
        fields: {
          age: value.age < 12 ? '太年轻了!' : undefined,
        },
      }
    },
  },
})
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value }) => (value % 2 === 0 ? '必须是奇数!' : undefined),
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
</template>

即使表单级别验证返回了 'Too young!' 错误,也只会显示 'Must be odd!

异步函数验证

虽然我们认为大多数验证都是同步的,但在许多情况下,网络调用或其他异步操作对验证很有用。

为此,我们有专门的 onChangeAsynconBlurAsync 和其他可用于验证的方法:

vue
<script setup lang="ts">
// ...

const onChangeAge = async ({ value }) => {
  await new Promise((resolve) => setTimeout(resolve, 1000))
  return value < 13 ? '您必须年满 13 岁才能创建账户' : undefined
}
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChangeAsync: onChangeAge,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @input="
          (e) =>
            field.handleChange((e.target as HTMLInputElement).valueAsNumber)
        "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<script setup lang="ts">
// ...

const onChangeAge = async ({ value }) => {
  await new Promise((resolve) => setTimeout(resolve, 1000))
  return value < 13 ? '您必须年满 13 岁才能创建账户' : undefined
}
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChangeAsync: onChangeAge,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @input="
          (e) =>
            field.handleChange((e.target as HTMLInputElement).valueAsNumber)
        "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

同步和异步验证可以共存。例如,可以在同一字段上同时定义 onBluronBlurAsync

vue
<script setup lang="ts">
// ...

const onBlurAge = ({ value }) => (value < 0 ? '无效值' : undefined)

const onBlurAgeAsync = async ({ value }) => {
  const currentAge = await fetchCurrentAgeOnProfile()
  return value < currentAge ? '您只能增加年龄' : undefined
}
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onBlur: onBlurAge,
      onBlurAsync: onBlurAgeAsync,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @blur="field.handleBlur"
        @input="
          (e) =>
            field.handleChange((e.target as HTMLInputElement).valueAsNumber)
        "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>
<script setup lang="ts">
// ...

const onBlurAge = ({ value }) => (value < 0 ? '无效值' : undefined)

const onBlurAgeAsync = async ({ value }) => {
  const currentAge = await fetchCurrentAgeOnProfile()
  return value < currentAge ? '您只能增加年龄' : undefined
}
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onBlur: onBlurAge,
      onBlurAsync: onBlurAgeAsync,
    }"
  >
    <template v-slot="{ field }">
      <label :for="field.name">年龄:</label>
      <input
        :id="field.name"
        :name="field.name"
        :value="field.state.value"
        type="number"
        @blur="field.handleBlur"
        @input="
          (e) =>
            field.handleChange((e.target as HTMLInputElement).valueAsNumber)
        "
      />
      <em role="alert" v-if="!field.state.meta.isValid">{{
        field.state.meta.errors.join(', ')
      }}</em>
    </template>
  </form.Field>
  <!-- ... -->
</template>

同步验证方法(onBlur)首先运行,异步方法(onBlurAsync)仅在同步方法(onBlur)成功时运行。要更改此行为,请将 asyncAlways 选项设置为 true,异步方法将无论同步方法的结果如何都会运行。

内置防抖

虽然异步调用是针对数据库验证的方法,但在每次按键时运行网络请求是 DDOS 数据库的好方法。

相反,我们通过添加单个属性来启用对 async 调用进行防抖的简单方法:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :async-debounce-ms="500"
    :validators="{
      onChangeAsync: async ({ value }) => {
        // ...
      },
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :async-debounce-ms="500"
    :validators="{
      onChangeAsync: async ({ value }) => {
        // ...
      },
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>

这将以 500ms 延迟对每个异步调用进行防抖。您甚至可以在每个验证属性上覆盖此属性:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :async-debounce-ms="500"
    :validators="{
      onChangeAsyncDebounceMs: 1500,
      onChangeAsync: async ({ value }) => {
        // ...
      },
      onBlurAsync: async ({ value }) => {
        // ...
      },
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :async-debounce-ms="500"
    :validators="{
      onChangeAsyncDebounceMs: 1500,
      onChangeAsync: async ({ value }) => {
        // ...
      },
      onBlurAsync: async ({ value }) => {
        // ...
      },
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>

这将每 1500ms 运行一次 onChangeAsync,而 onBlurAsync 将每 500ms 运行一次。

通过模式库进行验证

虽然函数为您的验证提供了更多的灵活性和自定义,但它们可能有点冗长。为了帮助解决这个问题,有一些库提供基于模式的验证,使简写和类型严格的验证变得更加容易。您还可以为整个表单定义单个模式并将其传递到表单级别,错误将自动传播到字段。

标准模式库

TanStack Form 原生支持所有遵循标准模式规范的库,最著名的是:

注意: 确保使用模式库的最新版本,因为旧版本可能还不支持标准模式。

验证不会为您提供转换后的值。有关更多信息,请参阅提交处理

要使用这些库中的模式,您可以像使用自定义函数一样将它们传递给 validators 属性:

vue
<script setup lang="ts">
import { z } from 'zod'
// ...

const form = useForm({
  // ...
})
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: z.number().gte(13, '您必须年满 13 岁才能创建账户'),
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>
<script setup lang="ts">
import { z } from 'zod'
// ...

const form = useForm({
  // ...
})
</script>

<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: z.number().gte(13, '您必须年满 13 岁才能创建账户'),
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>

表单和字段级别的异步验证也受支持:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: z.number().gte(13, '您必须年满 13 岁才能创建账户'),
      onChangeAsyncDebounceMs: 500,
      onChangeAsync: z.number().refine(
        async (value) => {
          const currentAge = await fetchCurrentAgeOnProfile()
          return value >= currentAge
        },
        {
          message: '您只能增加年龄',
        },
      ),
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: z.number().gte(13, '您必须年满 13 岁才能创建账户'),
      onChangeAsyncDebounceMs: 500,
      onChangeAsync: z.number().refine(
        async (value) => {
          const currentAge = await fetchCurrentAgeOnProfile()
          return value >= currentAge
        },
        {
          message: '您只能增加年龄',
        },
      ),
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>

如果您需要对标准模式验证进行更多控制,您可以像这样将标准模式与回调函数结合:

vue
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value, fieldApi }) => {
        const errors = fieldApi.parseValueWithSchema(
          z.number().gte(13, '您必须年满 13 岁才能创建账户'),
        )

        if (errors) return errors

        // 继续您的验证
      },
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>
<template>
  <!-- ... -->
  <form.Field
    name="age"
    :validators="{
      onChange: ({ value, fieldApi }) => {
        const errors = fieldApi.parseValueWithSchema(
          z.number().gte(13, '您必须年满 13 岁才能创建账户'),
        )

        if (errors) return errors

        // 继续您的验证
      },
    }"
  >
    <template v-slot="{ field }">
      <!-- ... -->
    </template>
  </form.Field>
  <!-- ... -->
</template>

防止提交无效表单

当提交表单时,onChangeonBlur 等回调也会运行,如果表单无效,提交将被阻止。

表单状态对象有一个 canSubmit 标志,当任何字段无效且表单已被触摸时,该标志为 false(canSubmit 在表单被触摸之前为 true,即使某些字段根据其 onChange/onBlur 属性在"技术上"无效)。

您可以通过 form.Subscribe 订阅它,并使用该值,例如,在表单无效时禁用提交按钮(在实践中,禁用的按钮不可访问,请使用 aria-disabled 代替)。

vue
<script setup lang="ts">
const form = useForm(/* ... */)
</script>

<template>
  <!-- ... -->

  <!-- 动态提交按钮 -->
  <form.Subscribe>
    <template v-slot="{ canSubmit, isSubmitting }">
      <button type="submit" :disabled="!canSubmit">
        {{ isSubmitting ? '...' : '提交' }}
      </button>
    </template>
  </form.Subscribe>
  <!-- ... -->
</template>
<script setup lang="ts">
const form = useForm(/* ... */)
</script>

<template>
  <!-- ... -->

  <!-- 动态提交按钮 -->
  <form.Subscribe>
    <template v-slot="{ canSubmit, isSubmitting }">
      <button type="submit" :disabled="!canSubmit">
        {{ isSubmitting ? '...' : '提交' }}
      </button>
    </template>
  </form.Subscribe>
  <!-- ... -->
</template>