在Vue项目的开发过程当中,Vue与element-ui能够说是项目开发标配。在学习Vue的时候,结合element-ui源码食用更佳。下面我会经过手撸element-ui中的Form组件,深刻分析Vue组件中的通讯方式。vue
<template> <el-form :model="info" :rules="rules" ref="forms" > <el-form-item label="用户名:" prop="userName"> <el-input v-model="info.userName" placeholder="请输入用户名"></el-input> </el-form-item> <el-form-item> <el-input type="password" v-model="info.userPassword" placeholder="请输入密码"></el-input> </el-form-item> <el-form-item> <button @click="save">提交</button> </el-form-item> </el-form> <template\> <script> data() { return { info: { userName:'', userPassword:'' }, rules: { userName: { required:true, message:'用户名不能为空' }, userPassword: { required:true, message:'密码不能为空' } } } }, methods: { save() { this.$refs.forms.validate((result) => { let message ='校验经过'; if (!result) { message ='校验未经过'; } alert(message) } } </script>
这是一个简单的用户名和密码的验证,在这里面,使用了如下的属性:git
参数 | 类型 | 说明 |
---|---|---|
model | object | 表单数据对象 |
rules | object | 表单验证规则 |
参数 | 类型 | 说明 |
---|---|---|
validate | Function(callback: Function(boolean, object)) | 对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未经过校验的字段。若不传入回调函数,则会返回一个 promise |
参数 | 类型 | 说明 |
---|---|---|
label | string | 标签文本 |
prop | string | 表单域 model 字段,在使用 validate、resetFields 方法的状况下,该属性是必填的 |
此次就经过这个例子展开分析,从源码级别分析分析该组件实现过程。github
好了,分析完基本需求以后,下面咱们开干。npm
咱们这里使用一个对数据进行异步校验的库async-validator,element-ui中也是使用的这个库。编程
input组件中须要实现双向绑定以及向上层el-form-item传递数据和通知验证。
// 双向绑定的input本质上实现了input而且接收一个value // 这里涉及到的vue组件通讯为$attrs,接受绑定传入的其余参数,如placeholder等 <template> <input :type="type" :value="value" @input="onInput" v-bind="$attrs" /> </template> <script> // 这里涉及到的vue组件通讯为provide/inject export default { props: { value: { type: String, default: ‘’, }, type: { type: String, default: 'text' } }, }, methods: { onInput(e) { this.$emit('input', e.target.value); // 通知父元素进行校验 使用this.$parent找到父元素el-form-item this.$parent.$emit('validate'); } } </script>
el-form-item组件做为数据验证中间件,要接受el-form中的数据,结合el-input中的数据根据el-form中的rules进行验证,并进行错误提示
<template> <div> <label v-text="label"></label> <slot></slot> <p v-if="error" v-text="error"></p> </div> </template> <script> // 引入异步校验数据的库 import Schema from 'async-validator'; // 这里涉及到的vue组件通讯为provide/inject export default { // 接收el-form组件的实例,方便调用其中的属性和方法 inject: ['form'], props: { label: { type: String, default: '', }, prop: { type: String, required: true, default: '' } }, }, data() { return { // 错误信息提示 error:'' } }, mounted(){ // 监听校验事件 this.$on('validate', () => { this.validate() }) }, methods: { // 调用此方法会进行数据验证,并返回一个promise validate() { // this.prop为验证字段,如: userName // 获取验证数据value,如: userName的值 const value = this.form.model[this.prop]; // 获取验证数据方法,如: { required:true, message:'用户名不能为空' } const rules = this.form.rules[this.prop]; // 拼接验证规则 const desc= { [this.prop]: rules }; // 实例化验证库 const schema = new Schema(desc); // 这里会返回一个promise return schema.validate( { [this.prop]: value }, errors => { if (errors) { this.error = errors[0].message; } else { this.error = ''; } } ) } } </script>
咱们上面分析过el-form只须要接受props值,并开放一个验证方法validate判断校验结果,而后把内嵌的slot内容展现出来,那么el-form实现就相对简单了
<template> <div> <slot></slot> </div> </template> <script> // 这里涉及到的vue组件通讯为provide/inject export default { // 由于上面需求分析提到,须要在form-item组件中进行验证,因此要将form实例总体传入form-item中,方便后续调用其方法和属性 provide() { return { form: this } }, props: { model: { type:Object, required:true, default: () => ({}), }, rules: { type:Object, default: () => ({}) } }, }, methods: { // 这是供外部调用的validate验证方法 接收一个回调函数 把验证结果返回出去 validate(callback) { // 使用this.$children找到全部el-form-item子组件,获得的值为一个数组,并调用子组件中的validate方法并获得Promise数组 const tasks = this.$children .filter(item => item.prop) .map(item => item.validate()); // 全部任务必须所有成功才算校验经过,任一失败则校验失败 Promise.all(tasks) .then(() => callback(true)) .catch(() => callback(false)) } } </script>
到这里Form组件的构建基本就结束了,这里涉及到的Vue组件通讯有不少,学习这部分源码能很大程度上的帮助咱们理解Vue中组件通讯的机制以及提高咱们的编程能力。element-ui
const boardcast = function (componentName, eventName, params) { this.$children.forEach(child => { let name = child.$options.componentName; if (componentName === name) { child.$emit.apply(child, [eventName].concat(params)); } else { boardcast.apply(child, [componentName, eventName].concat(params)); } }); } export default { methods: { // 向上寻找父级元素 dispatch(componentName, eventName, params) { let parent = this.$parent || this.$root; let 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)); } }, // 向下寻找子级元素 boardcast(componentName, eventName, params) { boardcast.call(this, componentName, eventName, params); } } };
使用mixin混入的方式,用这个方法对上面代码组件代码进行改造,能够解决查找父元素子元素的问题数组
通过改造后的完整代码在个人Github上,有须要的同窗能够去看一下,若是对你有帮助,欢迎star。
第一次写文章,里面可能还有不少问题须要改进,欢迎你们指正。
个人公众号是....忘了我没有公众号,hhhhhh。
最近在研究Vue源码,过年时间比较多,会陆续写一写Vue源码相关文章,好比Vuex,Vue-router的分析和简单实现。你们一块儿学习,一块儿进步。promise