手擼element-ui的Form組件一窺vue組件間通訊

在Vue項目的開發過程當中,Vue與element-ui能夠說是項目開發標配。在學習Vue的時候,結合element-ui源碼食用更佳。下面我會經過手擼element-ui中的Form組件,深刻分析Vue組件中的通訊方式。vue

element-ui中Form組件的簡單使用

<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

Form Attributes

參數 類型 說明
model object 表單數據對象
rules object 表單驗證規則

Form Methods

參數 類型 說明
validate Function(callback: Function(boolean, object)) 對整個表單進行校驗的方法,參數爲一個回調函數。該回調函數會在校驗結束後被調用,並傳入兩個參數:是否校驗成功和未經過校驗的字段。若不傳入回調函數,則會返回一個 promise

Form-Item Attributes

參數 類型 說明
label string 標籤文本
prop string 表單域 model 字段,在使用 validate、resetFields 方法的狀況下,該屬性是必填的

此次就經過這個例子展開分析,從源碼級別分析分析該組件實現過程。github

源碼實現需求分析

  • 實現一個el-form組件,其中接受model與rules兩個props,而且開放一個驗證方法validate,用於外部調用,驗證組件內容
  • 實現一個el-form-item組件,其中接受label與prop兩個props。且在這裏要注意的是el-form-item能夠做爲中間層,鏈接el-form與el-form-item中的的slot,並進行核心的驗證處理,因此數據驗證部分在這個組件中進行。
  • 實現一個el-input組件,實現雙向綁定,其中接受value與type兩個props

好了,分析完基本需求以後,下面咱們開幹。npm

校驗方法

咱們這裏使用一個對數據進行異步校驗的庫async-validator,element-ui中也是使用的這個庫。編程

el-input組件實現

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-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組件實現

咱們上面分析過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

組件實現中遺留的問題

  • 實現到這步其實還不能徹底放心,這個組件還不夠健壯。由於在組件源碼中還有一些處理在這裏尚未提到。
  • 若是在el-form組件中嵌套層級很深的話this.$children可能拿到的並非el-form-item,一樣el-input的this.$parent拿到的也不是el-form-item,那這個問題要怎麼處理呢?
  • 其實在vue 1.x中提供了兩個方法全局方法dispatch和boardcast,他能夠根據指定componentName來遞歸查找相應組件並派發事件,在vue 2.x中這個方法被廢棄了。可是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

相關文章
相關標籤/搜索