在Vue中,处理表单联动是业务代码中常见的需求。本文介绍了一个优雅的方法,通过自定义指令实现了在表单控件从页面上消失时,与其关联的字段值自动重置的功能。通过观察Element-UI的DOM结构,以及使用MutationObserver API,我们能够更智能地管理表单的状态。这样的实践使得代码更加清晰、模块化,并减少了大量重复的代码,提高了代码的可维护性。这种方法不仅适用于简单的联动需求,也可以处理复杂的表单联动逻辑,为开发者提供了更高效的开发体验。通过这个技巧,我们能够更加灵活地应对不同业务场景下的表单联动需求,提升了前端开发的效率和质量。
问题
在业务代码编写过程中,我们常常需要满足表单控件之间的联动展示需求。这样的联动经常涉及到根据选择的条件来展示相应的输入控件。在大多数情况下,提交到后端的数据就是页面上显示的输入控件中的数据。因此,在条件切换时,当前值关联的输入控件的内容应该被清空。
通常,我们会写出如下的代码
vue
<template>
<div>
<el-form :model="formData" label-width="80px">
<el-form-item label="条件选择" prop="condition">
<el-select v-model="formData.condition" @change="handleConditionChange">
<el-option label="条件A" value="A"></el-option>
<el-option label="条件B" value="B"></el-option>
</el-select>
</el-form-item>
<!-- 根据条件展示不同的组件 -->
<el-form-item v-if="formData.condition === 'A'" label="组件A" prop="inputValueA">
<el-input v-model="formData.inputValueA" />
</el-form-item>
<el-form-item v-else label="组件B" prop="inputValueB">
<el-input v-model="formData.inputValueB" />
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
formData: {
conditionA: '',
inputValueA: '',
inputValueB: '',
},
};
},
methods: {
handleConditionChange() {
this.formData.inputValueA = "";
this.formData.inputValueB = "";
},
},
};
</script>
上述代码在简单情况下是可行的,但当联动层级较多或表单联动穿插了更复杂的逻辑时,需要编写越来越多的 handleChange
方法。有没有一种方法,在控件从页面上消失时,它绑定的值也能够自动重置呢?
为了实现这个目标,我们可以采用如下的思路:
- 将输入控件关联的字段信息存储在对应的 DOM 上。
- 监听输入控件的 DOM 节点删除,从删除的节点中找到对应字段并进行重置。
对于第一点,通过观察 Element-UI 的 DOM 结构,我们发现 el-form-item
的 prop
会绑定到 DOM 结构的 label
的 for
属性上。
有了这个准备,接下来就是监听DOM节点的删除,直接MutationObserver
就完事了
MutationObserver是一个JavaScript的API,用于监测DOM树的变化。它提供了一种异步的方式来监听DOM元素的增加、删除、属性变化等操作,以及文本节点的修改。通过MutationObserver,开发者可以实时地捕捉到DOM的变化,并做出相应的响应。
完事具备,接下来就是要设计一个简单易用的使用方式了,封装为指令最为合适不过
ini
<el-form :model="formData" v-reset-fields>
</el-form>
指令的实现如下
javascript
Vue.directive('reset-fields', {
inserted(el, binding, vnode) {
const targetNode = el
const componentInstance = vnode.componentInstance
if (!componentInstance.model) {
throw new Error('reset-fields指令只能用于配置了model选项的el-form组件')
}
const config = { childList: true }
// 当观察到变动时执行的回调函数
const callback = (mutationsList) => {
if (mutationsList) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
if (mutation.removedNodes.length > 0) {
// 查出所有被移除的form-item包裹的表单字段名
const bindingValue = []
mutation.removedNodes.forEach((item) => {
let formItemEls = null
if (item.classList && item.classList.contains('el-form-item')) {
formItemEls = [item]
} else if (item.querySelectorAll) {
formItemEls = item.querySelectorAll('.el-form-item')
}
if (formItemEls) {
formItemEls.forEach((formItemEl) => {
const field = formItemEl.querySelector('label').getAttribute('for')
bindingValue.push(field)
})
}
})
// 重置字段
if (bindingValue.length > 0) {
const model = componentInstance.model
bindingValue.forEach((field) => {
if (Array.isArray(model[field])) {
model[field] = []
} else {
model[field] = undefined
}
})
}
}
}
}
}
}
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback)
// 以上述配置开始观察目标节点
observer.observe(targetNode, config)
// 保存观察器实例,用于解绑
el.mutationOb = observer
},
unbind(el) {
el.mutationOb.disconnect()
},
})