巧妙利用混入解决$parent/$children带来的耦合性
最近在跟着开课吧学习web全栈的课程,说实话真的感觉自己底子太差了,什么都“新鲜”的不行! 所以只能下功夫去努力了,整理一下学习中学到的方法,总结有错误的地方希望大家提醒一下,共同进步吧。
一、 利用vue实现element组件表单校验(目标)
课程就是为了实现这样一个小案例,那么就涉及到了父子组件中的传值问题了,到底用什么是最合适的的呢?就成了我们今天讨论的重点了!大概功能效果如图所示:
页面效果图
二、如何解决局部校验和全局校验中的父子通信
方案1:利用$parent/$children获取需要当前表单校验的组件(校验用的async-validator插件)
Kinput组件
<input :type="type" :value="value" @input="onInput">
methods: { onInput(e) { this.$emit('input', e.target.value) // 当数据发生变化,需要执行校验 // $parent 父组件只有一个插槽,所以让父组件去派发一个方法 this.$parent.$emit('validate') } },
KForm
provide(){ return{ form: this } }, props:{ model:{ type: Object, required:true }, rules:{ type: Object } }, methods: { validate(cb) { // 全局校验思路: // 方案1: $children // 1. 获取所有的Formitem const tasks = this.$children .filter(item => item.prop) .map(item => item.validate()); } },
//简单的组件结构 <template> <div> <KForm :model="model" :rules="rules" ref="form"> <KFormItem label="姓名" prop="username"> <KInput v-model="model.username"></KInput> </KFormItem> <KFormItem label="密码" prop="password"> <KInput v-model="model.password"></KInput> </KFormItem> <KFormItem > <button @click="login">登陆</button> </KFormItem> </KForm> </div> </template>
缺点: 以上方法实现这样的自定义组件结构是不会出错的,因为KForm组件下面只有KFormItem,没有其他的组件或者元素。但是如果出现了其他的子元素那么程序就会报错了,显然这样的方案对于用户的自定义是不适用的。
三、利用Vue混入来解决全局校验的功能
下面就是今天的重点了,我们要采用vue中非常好用的混入来解决这个问题
页面的组件dom不发生任何改变,我们可以去github上面查看element-ui源码是如何实现这样的功能的吧,
打开emitter.js文件里面是自定义的两个全局方法,
方法一“ 广播”
// broadcast 自上而下派发事件 // 参数1:组件名; 参数2: 事件名称; 参数3:参数数组 function broadcast(componentName, eventName, params) { // 遍历所有的子元素,如果子元素componentName和传入的相同则派发事件 this.$children.forEach(child => { var name = child.$options.componentName; if (name === componentName) { child.$emit.apply(child, [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); } }); }
方法二 “冒泡查询”
// dispatch 自下而上,冒泡查找conponentName相同的组件并派发事件 dispatch(componentName, eventName, params) { // 向上找父亲 var parent = this.$parent || this.$root; var name = parent.$options.componentName; // 向上查找直到找到相同名称的组件 while (parent && (!name || name !== componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; } } // 如果找到就派发事件,同时跳出循环 if (parent) { parent.$emit.apply(parent, [eventName].concat(params)); } },
在我们本地的src目录创建mixins/emitter.js文件,接下来我们来修改我们方案1代码的不健全性
修正input中$parent写法
1. mixins emitter
2. 声明componentName
3. dispath()
KInput.vue
import emitter from '@/mixins/emitter.js' export default { mixins: [emitter], }
methods: { onInput(e) { this.$emit('input', e.target.value) // 方案二: 冒泡查询并派发事件dispatch,我们的参数是动态的所以不用传递参数 // dispatch(componentName, eventName, params) {} this.dispatch('KFormItem', 'validate') } },
KInputItem.vue
mounted () { this.$on('validate', () =>{ this.validate() }) // 派发事件通知KForm,新增一个KFromItem实例 if(this.prop) { this.dispatch('KForm', 'kkb.form.addField', [this]) } },
KForm.vue
created(){ // 添加,记得做移除的操作 this.fields = [] this.$on('kkb.form.addField', item =>{ this.fields.push(item) }) },
methods: { validate(cb) { const tasks = this.fields.map(item => item.validate()); // 2. 执行他们的校验方法,如果大家的promise全部都resolve,校验通过 // 3. 如果其中有reject, catch() 中处理错误提示信息 Promise.all(tasks) .then( () => cb(true)) .catch( () =>cb(false)); } },
以上代码操作就可以实现父子之间传值的问题了。感觉总结的不太好,自己理解的不是很透彻,有时间了可以好好看看element-ui的源码!