VueUse 中的 useAsyncValidator:在 Vue 3 中优雅地封装 async-validator 表单校验
VueUse 中的 useAsyncValidator:在 Vue 3 中优雅地封装 async-validator 表单校验
useAsyncValidator 是 VueUse 生态(@vueuse/integrations)中针对 async-validator 的响应式封装组合式函数(Composable)。它让你以声明式的方式把校验规则与表单数据绑定起来:数据或规则一变化,校验自动触发,并把 pass、errors、errorFields、isFinished 等状态以响应式 Ref/Computed 形式暴露给模板。读完本文,你将掌握它的安装方式、完整类型签名、全部配置参数(immediate / manual / validateOption)、手动触发与 await 等待两种调用范式,并能结合仓库源码理解其底层实现原理,以及直接复用一个内置的渲染函数组件 UseAsyncValidator。
为什么需要 useAsyncValidator
async-validator 本身是一个与框架无关的 JavaScript 校验库,它支持同步与异步校验、字符串模板消息、自定义校验器(validator 函数)等强大能力。但直接使用它时,你需要自行处理:表单数据的响应式变化监听、校验实例的创建与销毁、错误信息的整理与存储、重复触发校验等琐碎逻辑。
useAsyncValidator 把这些细节全部收拢到一个组合式函数里,实现"数据即校验"的声明式体验:
value(表单数据)和rules(校验规则)都支持传入 Ref、Getter 或普通对象,内部通过toRef/toValue统一处理;- 默认在数据或规则变化时自动重新校验(深度监听,
deep: true); - 校验结果以可解构的响应式状态返回,模板中可直接渲染错误信息;
- 返回值同时实现了
PromiseLike接口,可以像 Promise 一样await,方便在提交逻辑中串联校验。
安装
useAsyncValidator 属于 @vueuse/integrations 包的集成功能,且将 async-validator 声明为可选的 peer dependency。因此在安装 @vueuse/integrations 之后,还需要额外安装 async-validator(v4):
npm i async-validator@^4
之所以要求
async-validator@^4,是因为实现中依赖Schema类与ValidateOption等 v4 的类型与 API。查看 integrations/package.json 可以看到async-validator: "^4"被列为peerDependencies且optional: true,意味着只有使用到该校验功能时才需要安装。
如果使用 pnpm 管理 monorepo,也可以参考仓库本身的做法,将 async-validator 加入依赖后通过子路径导入:
import { useAsyncValidator } from '@vueuse/integrations/useAsyncValidator'
该子路径在 package.json 的 exports 字段中有明确声明("./useAsyncValidator": "./dist/useAsyncValidator.js"),并且 integrations/index.ts 中同样通过 export * from './useAsyncValidator' 将其暴露在包的主入口下,因此 @vueuse/integrations 整体导入同样可用。
基本用法
最简单的用法是把普通对象形式的表单数据和规则传入,立即开始自动校验:
import { useAsyncValidator } from '@vueuse/integrations/useAsyncValidator'
import type { Rules } from 'async-validator'
import { reactive } from 'vue'
const form = reactive({ email: '', name: '', age: '' })
const rules: Rules = {
name: {
type: 'string',
min: 5,
max: 20,
required: true,
},
age: {
type: 'number',
required: true,
},
email: [
{
type: 'email',
required: true,
},
],
}
const { pass, isFinished, errorFields } = useAsyncValidator(form, rules)
在模板中,可以根据 errorFields 精确渲染每个字段的错误,并根据 pass 控制提交按钮的可用状态——这正是官方 demo.client.vue 演示的场景:
<div v-if="errorFields?.email?.length" text-red>
{{ errorFields.email[0].message }}
</div>
<button :disabled="!pass">
Submit
</button>
useAsyncValidator 的完整类型签名如下(与仓库中 index.ts 定义一致):
export type AsyncValidatorError = Error & {
errors: ValidateError[]
fields: Record<string, ValidateError[]>
}
export interface UseAsyncValidatorExecuteReturn {
pass: boolean
errors: AsyncValidatorError['errors'] | undefined
errorInfo: AsyncValidatorError | null
errorFields: AsyncValidatorError['fields'] | undefined
}
export interface UseAsyncValidatorReturn {
pass: ShallowRef<boolean>
isFinished: ShallowRef<boolean>
errors: ComputedRef<AsyncValidatorError['errors'] | undefined>
errorInfo: ShallowRef<AsyncValidatorError | null>
errorFields: ComputedRef<AsyncValidatorError['fields'] | undefined>
execute: () => Promise<UseAsyncValidatorExecuteReturn>
}
export interface UseAsyncValidatorOptions {
validateOption?: ValidateOption
immediate?: boolean // 默认 true
manual?: boolean
}
export declare function useAsyncValidator(
value: MaybeRefOrGetter<Record<string, any>>,
rules: MaybeRefOrGetter<Rules>,
options?: UseAsyncValidatorOptions,
): UseAsyncValidatorReturn & PromiseLike<UseAsyncValidatorReturn>
三个核心响应式状态的含义:
pass(ShallowRef<boolean>):当前校验是否通过;isFinished(ShallowRef<boolean>):最近一次校验是否已结束,可用于展示加载中的校验状态;errors/errorFields(ComputedRef):分别返回扁平的错误数组和按字段分组的错误对象,后者在表单场景下最常用;errorInfo(ShallowRef<AsyncValidatorError | null>):完整的错误对象,包含errors与fields两个属性;execute:手动执行一次校验,返回Promise<UseAsyncValidatorExecuteReturn>。
配置参数详解
immediate(默认 true)
是否在组合式函数创建时立即执行首次校验。当为 true 时,watch 以 immediate: true 的方式立即触发一次 execute;当为 false 时,首次校验会推迟到数据或规则首次变化之后。
从源码可以看出初始值的设计:pass 的初始值由 !immediate || manual 决定(index.ts)。这意味着:
immediate: true时pass初始为false,等首次异步校验完成后才更新;immediate: false或manual: true时pass初始为true,避免未校验前就把表单标记为"不通过"。
在测试 index.test.ts 的 immediate should can be work 用例中可以看到:{ immediate: false } 时 isFinished 与 pass 初始即为 true,且 errors 为空数组,验证了这一行为。
manual(默认 false)
是否完全关闭自动校验。当 manual: true 时,源码会跳过 watch 的注册(if (!manual) 分支,见 index.ts),校验只能通过调用 execute() 手动触发。这一模式适合"提交时统一校验"的场景,避免输入过程中频繁弹错。
测试中 set manual, do not run validator automatically 用例确认:设置 manual: true 后,即使规则明确不通过(name 长度不足 5),pass 依然保持 true、errors 保持空数组,直到手动调用 execute()。
注意:当 manual: true 时,immediate 参数实际上不生效(源码注释也写明 "Only works when manual is not set to true")。测试 set immediate and manual at the same time 用例专门覆盖了 { immediate: false, manual: true } 的组合,行为与纯 manual: true 一致。
validateOption
透传给 async-validator 的 schema.validate(data, options) 的配置对象(ValidateOption)。常见选项包括:
suppressWarning:是否抑制校验产生的警告输出;first:是否在遇到第一个错误时就立即返回,不再继续校验其余字段;firstFields:是否按字段维度在遇到第一个错误时即返回。
在 index.test.ts 的失败用例中,就用到了 validateOption: { suppressWarning: true } 来静默化预期中的校验失败告警:
const { pass, errors, isFinished } = await useAsyncValidator(form, rules, {
validateOption: {
suppressWarning: true,
},
})
手动执行与等待完成:execute 与 PromiseLike
手动触发 execute
当使用 manual: true 或需要在特定时机(如表单提交)触发校验时,调用 execute():
const { execute, pass, errors } = useAsyncValidator(form, rules, {
manual: true,
validateOption: { suppressWarning: true },
})
// 首次触发
await execute()
console.log(pass.value, errors.value)
// 修正数据后再次触发
form.name = 'okxiaoliang4'
await execute()
console.log(pass.value, errors.value) // true, []
测试 manual trigger validator 用例完整演示了这一流程:首次 execute() 后 pass 变为 false 并产生错误;修改数据再次 execute() 后 pass 变为 true、错误清空。
像 Promise 一样 await 整个返回值
useAsyncValidator 的返回值类型是 UseAsyncValidatorReturn & PromiseLike<UseAsyncValidatorReturn>。这意味着你可以直接 await 它,等待"初始校验完成"后再继续后续逻辑:
const { pass, errors, isFinished } = await useAsyncValidator(form, rules)
// 此处 initial 校验已完成
从源码看,这一能力由末尾的 then 方法实现(index.ts):内部用 until(isFinished).toBe(true)(来自 @vueuse/shared 的工具)等待 isFinished 变为 true,随后 resolve 出整个 shell(即完整的 UseAsyncValidatorReturn)。测试 should can be await 用例验证了 await 之后 isFinished、pass、errors 都已就绪。
底层执行流程
execute 的源码逻辑(index.ts)非常清晰:
- 先将
isFinished置为false、pass置为false; - 调用
validator.value.validate(valueRef.value, validateOption)执行校验,其中validator是一个 computed,内部根据当前rules创建Schema实例(new AsyncValidatorSchema(toValue(rules))); - 校验成功则
pass = true并清空errorInfo;校验失败则把抛出的错误(async-validator的校验错误对象)存入errorInfo; finally中把isFinished置回true;- 最后返回
{ pass, errorInfo, errors, errorFields }快照。
值得注意的细节是:errors 与 errorFields 是 computed,它们分别从 errorInfo.value?.errors 和 errorInfo.value?.fields 派生(index.ts)。当校验通过时 errorInfo 为 null,此时 errors 退化为空数组 []、errorFields 退化为空对象 {},这正是测试快照中"通过即空"的根源。
响应式联动
由于 value 和 rules 都可以是 Ref,自动校验模式下 watch 同时监听 [valueRef, validator] 并开启 deep: true(index.ts),所以:
- 修改
form.name等深层属性会触发重新校验; - 动态替换
rules(例如切换不同表单场景的规则)同样会触发重新校验。
测试 should reactive 用例正是先以 deepRef 传入表单与规则,等待初始校验失败后,将 form.value.name 改为合法长度,nextTick 后 pass 翻转为 true、errors 清空。
无渲染组件 UseAsyncValidator
除了组合式函数,仓库还提供了对应的无渲染(renderless)组件 UseAsyncValidator,定义在 component.ts,并从 @vueuse/integrations/useAsyncValidator/component 子路径导出。
它接收 form、rules、options 三个 props,内部通过 reactive(useAsyncValidator(...)) 把组合式函数的返回值变成响应式对象,并通过默认插槽向下传递:
<script setup lang="ts">
import { UseAsyncValidator } from '@vueuse/integrations/useAsyncValidator/component'
import type { Rules } from 'async-validator'
import { reactive } from 'vue'
const form = reactive({ email: '', name: '' })
const rules: Rules = {
email: { type: 'email', required: true },
name: { type: 'string', min: 5, required: true },
}
</script>
<template>
<UseAsyncValidator :form="form" :rules="rules">
<template #default="{ pass, isFinished, errorFields }">
<p>校验通过:{{ pass }},校验中:{{ !isFinished }}</p>
<p v-if="errorFields?.email?.length">
{{ errorFields.email[0].message }}
</p>
</template>
</UseAsyncValidator>
</template>
在需要把校验能力嵌入到纯模板(无 <script setup> 组合式逻辑)的场景中,这个组件是 useAsyncValidator 的等价替代。
常见用法对照
| 使用场景 | 配置 | 说明 |
|---|---|---|
| 实时校验(输入即校验) | 默认(immediate: true) |
数据变化自动校验,适合输入框即时反馈 |
| 提交时校验 | manual: true |
关闭自动监听,提交按钮回调中调用 execute() |
| 首次校验后 await | 直接 await useAsyncValidator(...) |
等待初始校验完成后继续异步流程 |
| 字段级错误展示 | 读取 errorFields |
按字段名取 ValidateError[],渲染 message |
| 静默失败告警 | validateOption: { suppressWarning: true } |
避免预期内的校验失败打印警告 |
总结
useAsyncValidator 的价值在于用约 130 行的实现(index.ts)把 async-validator 的"创建 Schema → 校验 → 收集错误"流程完全响应式化:MaybeRefOrGetter 形式的 value 与 rules 让它在 ref / reactive / getter 之间自由切换;immediate、manual、validateOption 三个参数覆盖了实时校验、提交时校验、定制校验行为三类主流需求;execute 与 PromiseLike 双通道让"手动触发"和"等待完成"两种异步范式都顺手可用。对于需要在 Vue 3 项目中快速获得健壮表单校验能力的团队,它是一个开箱即用的选择。如果想深入验证其行为,可以直接阅读仓库中的 index.test.ts,其中的 10 个用例完整覆盖了通过、失败、手动、响应式、await 等全部关键路径。
</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>