在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