前面我们完成了表单构建神器FormKit的项目初始化和功能初体验,本小节我们着重来看下FormKit的校验能力。先来看内置的关联校验功能,然后分析下设计的一些局限,再通过源码的fork方式来扩展出更灵活的校验。
用户注册表单
看这样一个schema定义:
ts
const formSchema = {
$formkit: 'form',
children: [{
$formkit: 'text',
name: 'username',
label: '用户名',
help: '网站注册的用户名,不是昵称'
}, {
$formkit: 'text',
name: 'email',
label: '邮箱',
help: '注册该网站的邮箱',
validation: 'email',
}, {
$formkit: 'password',
name: 'password',
label: '密码',
help: '请输入密码',
validation: 'required',
}, {
$formkit: 'password',
name: 'password_confirm',
label: '确认密码',
help: '请输入确认密码',
validation: 'required|confirm',
}, {
$formkit: 'checkbox',
name: 'agree',
label: '我同意该网站的条款',
},
]}
渲染出来的表单效果:

默认关联校验的局限性
从上面例子可以看出,密码的confirm校验,要求字段的命令遵循约定,这种方式就无法让用户自由定义字段了,同样的问题还有官方下面这个示例,关联字段的校验规则需要定义多处,维护麻烦,我们可以基于它源码底层的处理机制来扩展。

基于formkit源码构建
现在我们引入了formkit的源码来进行二次开发。
先确保导入最新的FormKit2.0.0源码模块,整个工程安装和构建ok!

然后启动服务

扩展联动校验
先说下机制与 confirm 同源:规则执行时 observe → 读兄弟 → stopObserve → 挂监听 → 依赖变化 → revalidate。
在具体实现之前,先往低代码表单设计器要的数据形式上靠,先把前面的schema维护到一个json中,并进行引入使用:
ts
import formSchema from '../json-schema/003_user_register.schema.json'
这样我们可以扩展出很灵活的表单元素级校验定义形式:
json
{
...
"validation": "required|maxLength:8|myFn1|myFn2",
"validationRules": {
"myFn1": "函数体",
"myFn2": "函数体"
}
}
可以控制多重校验的执行顺序,同时也可以灵活定义,函数体中可以使用if、else的判断形式,对于校验失败的不同情况,可以设计为抛出不同消息的异常。这里对于多字段关联校验,可以借助于node.at('...').value的形式来获取关联字段的值,非常重要的一点,是通过这种方式可以建立起依赖字段框的监听,确保依赖字段的输入会整体影响到关联校验,确保校验结果的一致性。
常见的关联校验诸如:多个字段必须同时不会空、多个字段确保不都为空、两个字段确保校验相等、列表项累加求和满足一定的范围区间等等。将校验规则的扩展定义权交给管理员用户。
支持自定义校验结构
首先在密码确认框中,在必填校验的基础上加一个自定义的校验,先做最简单的尝试,配置如下:
json
{
"$formkit": "password",
"name": "passwordConfirm",
"label": "确认密码",
"help": "请输入确认密码",
"validation": "required|confirmPwd",
"validationRules": {
"confirmPwd": " console.log('ok!!!'); return true "
}
}
注意这里我们是手动编辑函数体的,语法的分隔或换行不可忽视,不然有语法错误。
先确保formkit源码支持这样的拓展,按 packages/validation/src/validation.ts 里 createValidationPlugin 的真实结构,进行最小侵入改法:只在合并 availableRules 之前把 validationRules 里的 string → function,而不用改 parseRules / runRule / observer,确保执行路径与formkit原先的实现机制完全一致。
具体的改动这里省略。
温馨提示 源码二开只要自己把控好思路,交给Cursor AI这样的代码生成器来改就行了,高效又省事。改完后自己手动构建编译和测试。
注意,关于这里的函数定义语法解析可以采用的思路:采用 Acorn + astring 进行顶层 AST 归一化,并添加必要依赖。
接下来我们做一个测试,先看下自定义校验涉及到的定义:
json
{
"$formkit": "password",
"name": "passwordConfirm",
"label": "确认密码",
"help": "请输入确认密码",
"validation": "required|length:4,10|confirmPwd",
"validationRules": {
"confirmPwd": " console.log('ok!!!'); return true "
}
}
当前面的校验通过时,才会触发自定义的校验,也就是密码长度介于4-10之间时咱们的第三重自定义校验生效,看效果:

现在我们再尝试着调下规则:"validation": "required|confirmPwd|length:4,10"
现在只要输入不为空就会触发自定义校验:
只有当退格删除到只剩一个字符再删除就不会触发,因为不满足非空条件了。
抛出自定义错误类型
对于FormKit默认的静态错误消息模板的映射错误消息方式,让配置过于分散不灵活、不利于低代码的集中维护错误逻辑。因此这里我们扩展出,通过抛异常的方式来抛出错误,做到按照不同的逻辑分支动态抛出不同的错误。这种方式类似于Element Plus表单的自定义校验,只是它的API多了一层callback调用:callback(new Error('Error Message'))。
看看我们的json配置中的定义,这里我们二话不说就抛出错误,先不考虑校验逻辑,直接看效果:
json
{
"$formkit": "password",
"name": "passwordConfirm",
"label": "确认密码",
"help": "请输入确认密码",
"validation": "required|length:4,10|confirmPwd",
"validationRules": {
"confirmPwd": " console.log('ok!!!'); throw new FormKitValidationError('这是来路不明的自定义校验失败信息') "
}

json文件编辑器
现在我们做一个大胆的尝试,来做一个所见即所得的json表单编辑器,顺便验证下我们自定义校验规则的增量实时渲染能力:

关联字段校验逻辑
现在是时候完成关联字段校验了,对于关联字段我们通过node.at('...').value的调用获取兄弟节点来判断,这在formkit底层会自动为关联字段组件简历依赖监听,当关联字段更新时也会自动触发本校验。
看下两个密码框的json定义:
json
{
"$formkit": "password",
"name": "password",
"label": "密码",
"help": "请输入密码",
"validation": "required|length:4,10"
},
{
"$formkit": "password",
"name": "passwordConfirm",
"label": "确认密码",
"help": "请输入确认密码",
"validation": "required|length:4,10|confirmPwd",
"validationRules": {
"confirmPwd": "if (node.at('password').value !== node.value) throw new FormKitValidationError('两次输入的密码不一致'); return true"
}
}
当完成了前两个基础校验后,我们自定义的校验就会触发,不管时在密码确认框还是在密码框上输入都会触发,这就是使用了formkit的关联校验字段依赖追踪机制。

那除了密码确认关联校验,我们再对多个字段进行不全部为空的校验就变得轻而易举了,现在我们定义三个关联字段:用户名、邮箱和手机号,要验证这三个字段不能都为空,先看定义:
json
{
"$formkit": "text",
"name": "username",
"label": "用户名",
"help": "网站注册的用户名,不是昵称"
},
{
"$formkit": "text",
"name": "email",
"label": "邮箱",
"help": "注册该网站的邮箱",
"validation": "email"
},
{
"$formkit": "text",
"name": "mobile",
"label": "手机号",
"help": "注册该网站的手机号"
}

这里我们只对邮箱字段做了格式校验,非空校验规则由三个字段来决定,可以只在用户名字段上加联合校验规则:
json
{
"$formkit": "text",
"name": "username",
"label": "用户名",
"help": "网站注册的用户名,不是昵称",
"validation": "+checkNotAllEmpty",
"validationRules": {
"checkNotAllEmpty": "if (!node.at('email')?.value && !node.at('mobile')?.value && !node.value) throw new FormKitValidationError('用户名、邮箱和手机号不能都为空'); return true"
}
}
看下效果:
现在这三个字段可以联动校验了,被联动的字段自身的校验和联动校验是不冲突的,不会相互影响,可以协调工作。
通过本文对FormKit校验模块源码的二开,我们很好的实现了用户自定义校验的灵活自由控制,借助于FormKit的底层的依赖字段追踪机制,很好的做到了"一处定义,多处联动",为我们后续的低代码表单设计器的研发补全了基础且核心的字段校验扩展机制。我是小卷,一个爱学习分享的搬砖老码农,喜欢就关注我,我们下期再见!
