🚀 基于 provide/inject 的表单组校验 —— 在老项目中实现最小入侵式增强
在一些业务复杂、UI 自定义程度极高的项目中,即使前端团队很有经验,也常会遇到类似的现实情况:
- UI 组件库并不能满足复杂或个性化的布局需求
- 表单组件完全是手工打造的
- 表单校验逻辑也属于“自研”,缺乏统一扩展机制
在这种情况下,如果突然新增一个“跨多表单项联动校验”的需求,很容易让整个校验体系崩掉,或需要大面积重构。
本文分享的方案,正是在 无需大规模改造既有组件 的前提下,借助 Vue 的 provide/inject 特性,为项目加入了一个 “表单组校验组件”,实现以下需求:
一组连续的表单项:全部都没填时无需校验,但只要填写其中一个,其他项必须补全,才能通过校验
例如示例页面中的“家庭联络人2”,若全部字段为空则允许提交,但若填写“姓名”,则职务/单位/手机等也必须填写。

1️⃣ 需求背景
项目中表单 UI 和校验都是自研的,表单项组件为 InputItem.vue。
核心需求:
- 相邻的多个字段可以组成一个 校验组 ValidateGroup
- 当组内所有字段都为空 → 不触发必填校验
- 当任意字段填写 → 所有字段均变为必填
- 校验提示逻辑仍留在 InputItem 内部,不改造父级提交逻辑
这类规则非常常见:
- 紧急联系人: 姓名、电话
- 工作经历:公司、岗位、时间段
- 婚姻情况:配偶姓名、配偶电话
- 邮寄地址:省、市、区、详细地址
2️⃣ provide/inject:为什么能解决问题?
Vue 2 中的 provide/inject 本质上为任意层级的组件提供了一种简单的“依赖注入”机制:
- 父组件 provide 某些能力(值/方法)
- 子组件 inject 后直接使用
对于这里的场景:
- 每个
ValidateGroup.vue提供一个“组的上下文” - 每个
InputItem.vue注入这个上下文,就知道自己属于哪个组 - 组内统一处理“要不要触发必填校验”的逻辑
也就是说:
页面 → Provide(校验组)
→ InputItem1(inject)
→ InputItem2(inject)
→ InputItem3(inject)
这就避开了“跨多个 InputItem 互相通信”的麻烦。
3️⃣ ValidateGroup.vue —— 只做一件事:统计组内是否有值
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
provide() {
return {
ValidateGroup: this
}
},
data() {
return {
hasValueMemberCount: 0,
};
},
computed: {
shouldRequireValidate() {
return this.hasValueMemberCount > 0;
}
},
methods: {
add() {
this.hasValueMemberCount++
},
minus() {
this.hasValueMemberCount--
},
}
};
</script>
它负责的逻辑非常纯粹:
- 记录组内有值的字段数量
hasValueMemberCount - 只要该值 > 0,则所有项目都必须参与必填验证
4️⃣ InputItem.vue —— 最小修改,却获得“组级校验能力”
项目本身已有完善的表单校验逻辑,因此我们做了“最小入侵”的增强:
① 注入 ValidateGroup
inject: {
ValidateGroup: { default: null }
}
② 监听 value 变化,向组汇报状态变化
handleMemberValueChangeForGroup(newVal, oldVal) {
if (oldVal === "" && newVal !== "") {
this.ValidateGroup.add();
}
if (oldVal !== "" && newVal === "") {
this.ValidateGroup.minus();
}
}
③ 校验时判断:是否跳过 required 校验?
修改校验逻辑中的“空值处理”部分:
if (
rule.empty &&
this.value == "" &&
this.isSkipMemberRequireValidate()
) {
return true;
}
即:
- rule.empty = 非必填(允许为空,空值不校验)
- value 空
- 组内没有其他值(跳过成员必填校验)
→ 可以跳过校验
对应地,加一个判断方法:
isSkipMemberRequireValidate() {
if (this.hasValidateGroup && this.ValidateGroup.shouldRequireValidate) {
return false;
}
return true;
}
这样 InputItem 根本不需要知道它所在组里有哪些字段,只需要判断:
- “组内其他成员有没有值?”
5️⃣ 页面使用 —— 最自然的方式
只需包一层 <validate-group>:
<validate-group>
<input-item title="姓名" v-model="guardianName2" :rules="guardianNameRules2"/>
<input-item title="关系" v-model="relation2" :rules="relationRules2"/>
<input-item title="职务" v-model="job2" :rules="guardianJobRules2"/>
<input-item type="textarea" title="工作单位" v-model="work2" :rules="guardianWorkRules2"/>
<input-item title="手机号码" v-model="telephoneNum2" :rules="guardiantelephoneNumRules2"/>
</validate-group>
无需额外传参,无需在父组件写任何额外逻辑。
所有“跨字段联动校验”逻辑,都封装在 ValidateGroup + InputItem 内部完成。
6️⃣ 最终效果
| 是否填写任意字段 | 其他字段是否必填 | 结果 |
|---|---|---|
| 全部为空 | ❌ 不校验 | 提交通过 |
| 填写任意一个字段 | ✔ 其他字段全部校验必填 | 若未填完整 → 不通过 |
这正是表单“关联必填”最常见的使用场景。
🧩 总结:一次优雅的“最小入侵式增强”
这个方案的优点非常显著:
✔ 无需修改表单外层的业务代码
✔ 无需调整 InputItem 的 props 或现有调用方式
✔ 可对任意一组 InputItem 增强联动校验能力
✔ 校验逻辑保持独立、可复用、可扩展
✔ 长期可维护,并可灵活支持更多复杂校验规则
这正是 provide/inject 在老项目中最适合发挥作用的场景之一:
帮助我们在不破坏既有代码结构的前提下,实现跨层级、跨组件的逻辑共享与增强。

浙公网安备 33010602011771号