vue表單驗證你真的會了嗎?元件之表單驗證(form)validate

貼上複製大前端發表於2019-04-04

前言

很久沒有寫文章了,學習了一下webpack,基礎的一些元件,今天帶來form表單驗證元件(element.iviewui)的一期教程(作為一個菜雞畢竟經歷眾多專案可以給一些新手一點提示 (QQ群技術討論)838293023備註(github進來的

游泳健身瞭解一下:github 技術文件 技術文件會持續更新


內容總結

  1. 原理解釋
  2. 派發和廣播 // 父子級互動
  3. 引入 async-validator // 表單驗證外掛
  4. form/以及api設計
  5. form-item
  6. input

效果圖

vue表單驗證你真的會了嗎?元件之表單驗證(form)validate

1.原理解釋

原理

考慮

我們看一下我們可以用form去整體觸發校驗也可以單個input來觸發form-item 進行校驗 童鞋們現在可能感覺還是沒懂,沒關係繼續往下看。

2.派發和廣播

為什麼要用廣播和派發呢。通常我們和業務沒有關係的元件儘量不要使用vuex和bus(事件匯流排)。 下面我送上廣播和派發的程式碼。我們在需要呼叫元件綁上this.$on('event',res=>()),通過派發和廣播進行呼叫$emit

  1. 派發是向上查詢且只呼叫1個
  2. 派發是向下查詢呼叫多個
  3. 注意⚠️所有的元件都要寫上name
  4. 通過混合器 mixins 來使用
emitter.js
/**
 * 遞迴使用 call 方式this指向
 * @param componentName // 需要找的元件的名稱
 * @param eventName // 事件名稱
 * @param params // 需要傳遞的引數
 */
function broadcast(componentName, eventName, params) {
    // 迴圈子節點找到名稱一樣的子節點 否則 遞迴 當前子節點
    this.$children.map(child=>{
        if (componentName===child.$options.name) {
            child.$emit.apply(child,[eventName].concat(params))
        }else {
            broadcast.apply(child,[componentName,eventName].concat(params))
        }
    })
}
export default {
    methods: {
        /**
         * 派發 (向上查詢) (一個)
         * @param componentName // 需要找的元件的名稱
         * @param eventName // 事件名稱
         * @param params // 需要傳遞的引數
         */
        dispatch(componentName, eventName, params) {
            let parent = this.$parent || this.$root;//$parent 找到最近的父節點 $root 根節點
            let name = parent.$options.name; // 獲取當前元件例項的name
            // 如果當前有節點 && 當前沒名稱 且 當前名稱等於需要傳進來的名稱的時候就去查詢當前的節點
            // 迴圈出當前名稱的一樣的元件例項
            while (parent && (!name||name!==componentName)) {
                parent = parent.$parent;
                if (parent) {
                    name = parent.$options.name;
                }
            }
            // 有節點表示當前找到了name一樣的例項
            if (parent) {
                parent.$emit.apply(parent,[eventName].concat(params))
            }
        },
        /**
         * 廣播 (向下查詢) (廣播多個)
         * @param componentName // 需要找的元件的名稱
         * @param eventName // 事件名稱
         * @param params // 需要傳遞的引數
         */
        broadcast(componentName, eventName, params) {
            broadcast.call(this,componentName, eventName, params)
        }
    }
}
複製程式碼

3.async-validator

不懂async-validator 可以去官網看看 github

yarn add async-validator // 因為當前這個外掛是需要打包到專案裡的所以不能加-D
複製程式碼

4.api設計

我們看一下下面element官網的圖`

  1. form有2個注入的欄位:rules規則,和:model當前form的值會通過model的值和rules進行匹配來進行校驗.
  2. form-item有2個注入的欄位lableprop(prop)是來和form進行匹配來獲取當前的form-item的值的
  3. input 其實有當前的@input的方法。v-model就不解釋了

vue表單驗證你真的會了嗎?元件之表單驗證(form)validate

form

  1. 我們在form先開始注入當前所有的form-item例項(獲取)
  2. created 會在生命週期開始的時候繫結和刪除當前例項的方法。通常繫結都在頁面dom開始前呼叫需要在dom載入完
  3. provide配合inject 使用讓子元件可以呼叫當前父元件的方法以及data
  4. 下面都寫了備註可以放心食用(經過測試當前是可以進行校驗的)
form.vue
<template>
    <form>
        <slot></slot>
    </form>
</template>

<script>
    export default {
        name: "aiForm",
        provide(){ //  [不懂的可以看看](https://cn.vuejs.org/v2/api/#provide-inject)
            return {
                form: this
            }
        },
        props: {
            // 當前 form 的model
            model: {
                type: Object
            },
            // 驗證
            rules: {
                type: Object
            }
        },
        data(){
            return{
                fields: [] // 儲存當前的 form-item的例項
            }
        },
        created(){
            // 存當前例項
            let that =this;
            this.$on('on-form-item-add',item=>{
                if (item) {
                    that.fields.push(item)
                }
            });
            // 刪除當前有的例項
            this.$on('on-form-item-remove',item=>{
                if (item.prop) {// 如果當前沒有prop的話表示當前不要進行刪除(因為沒有注入)
                    that.fields.splice(that.fields.indexOf(item),1)
                }
            })
        },
        methods:{
            /**
             * 清空
             */
            resetFields(){//新增resetFields方法使用的時候呼叫即可
                /**
                 * 當前所有當form-item 進行賦值
                 */
                this.fields.forEach(field => {
                    field.resetField();
                });
            },
            /**
             * 校驗 公開方法:全部校驗資料,支援 Promise
             */
            validate(callback){
                return new Promise(resolve=>{
                    /**
                     * 當前所有當form-item 進行校驗
                     */
                    let valid = true; // 預設是通過
                    let count = 0; // 來匹配當前是否是全部檢查完
                    this.fields.forEach(field => {
                        // 每個例項都會有 validation 的校驗的方法
                        field.validation('',error=>{
                            // 只要有一個不符合那麼當前的校驗就是未通過的
                            if (error) { 
                                valid = false;
                            }
                            // 通過當前檢查完所有的form-item的時候才會呼叫
                            if (++count === this.fields.length) {
                                resolve(valid);// 方法使用then
                                if (typeof callback === 'function') {
                                    callback(valid);// 直接呼叫注入的回撥方法
                                }
                            }
                        });
                    });
                })
            }
        }
    }
</script>

複製程式碼

5.form-item

  1. form-item比較複雜我們一個一個講
  2. isRequired來判斷當前是否需要必填
  3. validateState來判斷當前校驗的狀態
  4. validateMessage當前的錯誤的值
  5. inject: ['form'] 我們就可以通過this.from.xxx來呼叫父元件的事件以及值了
  6. computed下的fieldValue可能在不停的變化所以我們通過計算屬性來使用
  7. initialValue 預設的值我們在mounted的時候且當前需要進行校驗的時候(prop有的時候)會賦值
  8. mixins: [Emitter]混合器就是裡面的方法以及date都可以在當前呼叫使用頻繁的都可以放在混合器裡面
  9. 我們form-item 會傳入input的兩個方法blurchange(input原生使用的@input)通過form傳入的校驗rules裡面的trigger來判斷
form-item.vue
<template>
    <div>
        <label :class="isRequired?'ai-form-item-label-required':''">{{label}}</label>
        <div>
            <slot></slot>
            <div class="ai-form-item-message" v-if="validateState==='error'">{{validateMessage}}</div>
        </div>
    </div>
</template>

<script>
    import Emitter from '../../mixins/emitter';
    import schema from 'async-validator';
    export default {
        name: "aiFormItem",
        mixins: [Emitter],
        inject: ['form'],
        props: {
            label: {
                type: String,
                default: ''
            },
            prop:{
                type: String
            },
        },
        computed:{
            fieldValue () {
                return this.form.model[this.prop];
            },
        },
        data(){
            return {
                initialValue: '', // 儲存預設值
                isRequired: false, // 當前的是否有問題
                validateState: '', // 是否校驗成功
                validateMessage: '', // 校驗失敗文案
            }
        },
        methods:{
            /**
             * 繫結事件 進行是否 required 校驗
             */
            setRules(){
                let that = this;
                let rules = this.getRules();//拿到父元件過濾後當前需要使用的規則
                if (rules.length) {
                    // every 方法用於檢測陣列所有元素是否都符合指定條件(通過函式提供)
                    // some 只要有一個符合就返回true
                    this.isRequired = rules.some(rule=>{
                        // 如果當前校驗規則中有必填項,則標記出來
                        return rule.required;
                    })
                }
                /**
                 * blur 事件
                 */
                this.$on('on-form-blur',that.onFieldBlur);
                /**
                 * change 事件
                 */
                this.$on('on-form-change',that.onFieldChange)
            },
            /**
             * 從 Form 的 rules 屬性中,獲取當前 FormItem 的校驗規則
             */
            getRules () {
                let that = this;
                let rules = that.form.rules;
                rules = rules?rules[that.prop]:[];
                return [].concat(rules||[])//這種寫法可以讓規則肯定是一個陣列的形式
            },
            /**
             * Blur 進行表單驗證
             */
            onFieldBlur(){
                this.validation('blur')
            },
            /**
             * change 進行表單驗證
             */
            onFieldChange(){
                this.validation('change')
            },
            /**
             * 只支援 blur 和 change,所以過濾出符合要求的 rule 規則
             */
            getFilteredRule (trigger) {
                let rules = this.getRules();
                // !res.trigger 沒有呼叫方式的時候預設就校驗的
                // filter 過濾出當前需要的規則
                return rules.filter(res=>!res.trigger || res.trigger.indexOf(trigger)!==-1)
            },
            /**
             * 校驗資料
             * @param trigger 校驗型別
             * @param callback 回撥函式
             */
            validation(trigger,callback=function () {}){
                // blur 和 change 是否有當前方式的規則
                let rules = this.getFilteredRule(trigger);
                // 判斷當前是否有規則
                if (!rules || rules.length === 0) {
                    return
                }
                // 設定狀態為校驗中
                // async-validator的使用形式
                this.validateState = 'validating';
                var validator = new schema({[this.prop]: rules});
                // firstFields: true 只會校驗一個
                validator.validate({[this.prop]: this.fieldValue}, { firstFields: true },(errors, fields) => {
                    this.validateState = !errors ? 'success' : 'error';
                    this.validateMessage = errors ? errors[0].message : '';
                    callback(this.validateMessage);
                });
            },
            /**
             * 清空當前的 form-item
             */
            resetField(){
                this.form.model[this.prop] = this.initialValue;
            }
        },
        // 元件渲染時,將例項快取在 Form 中
        mounted(){
            // 如果沒有傳入 prop,則無需校驗,也就無需快取
            if (this.prop) {
                this.dispatch('aiForm','on-form-item-add', this);
                // 設定初始值,以便在重置時恢復預設值
                this.initialValue = this.fieldValue;
                // 新增表單校驗
                this.setRules()
            }
        },
        // 元件銷燬前,將例項從 Form 的快取中移除
        beforeDestroy(){
            this.dispatch('iForm', 'on-form-item-remove', this);
        },
    }
</script>

<style scoped>
    <!--當前css-->
    .ai-form-item-label-required:before{
        content: '*';
        color: red;
    }
    .ai-form-item-message {
        color: red;
    }
</style>
複製程式碼

5.input

  1. value 支援一個入參
  2. 因為當前是一個input注入的引數是不能直接放到input裡面使用的所以先賦值給了defaultValue然後用watch來不停給defaultValue賦值達到一個父元件修改後的一個繫結
<template>
   <input type="text"
          @input="handleInput" // change
          @blur="handleBlur"
          :value="defaultValue"
   >
</template>

<script>
   import Emitter from '../../mixins/emitter.js'
   export default {
       name: "aiInput",
       mixins: [Emitter],
       props: {
           value: {
               type: String,
               default: ''
           }
       },
       data(){
           return {
               defaultValue: this.value
           }  
       },
       watch:{
           value (val) {
               this.defaultValue = val;
           }
       },
       methods:{
           /**
            * change 事件
            * @param event
            */
           handleInput(event){
               // 當前model 賦值
               this.defaultValue = event.target.value;
               // vue 原生的方法 return 出去
               this.$emit('input',event.target.value);
               // 將當前的值傳送到 aiFormItem 進行校驗
               this.dispatch('aiFormItem','on-form-change',event.target.value)
           },
           /**
            * blur 事件
            * @param event
            */
           handleBlur(event){
               // vue 原生的方法 return 出去
               this.$emit('blur',event.target.value);
               // 將當前的值傳送到 aiFormItem 進行校驗
               this.dispatch('aiFormItem','on-form-blur',event.target.value)
           }
       }
   }
</script>

複製程式碼

最後

最後給上一個當前可以的使用方式

<template>
 <div class="home">
   <button @click="changeButton">測試</button>
   <ai-form ref="formItems" :model="formValidate" :rules="ruleValidate">
     <ai-form-item label="使用者名稱" prop="name">
       <ai-input  v-model="formValidate.name"/>
     </ai-form-item>
   </ai-form>
 </div>
</template>

<script>
 import AiForm from "../components/form/form";
 import AiFormItem from "../components/form/form-item";
 import AiInput from "../components/input/ai-input";
export default {
   name: 'home',
   components: {AiInput, AiFormItem, AiForm},],
   data(){
       return{
           formValidate: {
               name: '123z',
               mail: ''
           },
           ruleValidate: {
               name: [
                   { required: true, message: '使用者名稱不能為空', trigger: 'blur' },
               ],
           }
       }
   },
   methods:{
       changeButton(){
           this.$refs.formItems.resetFields() // 清空方法
           this.$refs.formItems.validate() // 驗證方法
               .then(res=>{
                   console.log(res)
               })
       }
   },
}
</script>

複製程式碼

小結

可能現在小夥伴還是不懂。。俗話說;師傅領進門,修行在個人。程式碼上的備註寫的也夠多了。還是不懂的可以加群問問小夥伴們,

相關文章