從零實現一個Vue表單驗證外掛

FatGe發表於2019-03-04

當我們在業務中碰到痛點問題的時候,會導致部分程式碼邏輯不清晰。這時就需要用皮鞭、蠟燭去解決它、調教它,將它的邏輯變得清晰起來。

概述:上篇文章介紹了,如何函式式呼叫表單元件從而減少維護其狀態的方法基於Vue構造器建立Form元件的通用解決方案。現在來介紹下如何處理表單驗證問題,在大多數與後端通訊的場景中,表單驗證是一個不可避免的問題。它承載了許多的邏輯以及狀態,在某些過於複雜的場景中,會使得導致程式碼極其臃腫。校驗的規則與狀態的處理耦合在一起,導致後續難以接手和開發。


1. 前言

現有的Vue表單驗證外掛有vuelidatevee-validate等,但是如果場景簡單的話,沒有引入外掛的必要,但是場景複雜的話,驗證規則又要定製化的時候,應用起來沒那麼順手。就像初次調教的人,就不能上皮鞭。所以,如何調教一個屬於自己的,聽話的Vue表單驗證外掛,是十分有必要的。

2. 例項以及使用規則

例項:fatge.xyz/blog/juejin…

例項程式碼:GitHub - FatGe/fat-validator: fat-validator-demo

原始碼:GitHub - FatGe/fat-validator: fat-validator

程式碼中,有

<template>
    <input 
        placeholder="請輸入"
        v-model="form.nativeInput"
        v-validate:nativeInput="validates.nativeInput"
    />
	<span class="u-info">{{ errors.get('nativeInput').warn }}</span>
</template>
<script>
import { validateResult } from 'fat-validator'

export default {
    mixins: [ validateResult ],
    data () {
        return {
            form: {
                nativeInput: ''
            },
            validates: {
                nativeInput: [{
                    need: () => !!this.form.nativeInput,
                    warn: '不能為空'
                }]
            }
        }
    }
}
</script>
複製程式碼

上述程式碼中,v-validate:nativeInput.blur="validates.nativeInput"自定義指令中

  • arg(nativeInput)代表著校驗的結果的key;

  • modifiers(blur)代表著失焦校驗;

  • value(validates.nativeInput)代表著data中維護的具體的校驗規則。​

input元素失焦時,會自動進行校驗,將校驗結果errors.get('nativeInput').warn顯示在頁面中。

3. 原理

上述自定義指令的主要原理如下圖

資料流

也就是元件通過自定義指令v-*,將資料以及驗證規則傳遞給處理Handler,Handler將結果返回給元件,具體如下:

  • 元件通過自定義指令將資料以及校驗規則傳遞給Handler:

    從例項中的v-validate:nativeInput.blur可以看出,選用了自定義指令作為連線被校驗元件和校驗規則之間的橋樑,主要是它兩個特點:

    • 鉤子函式:Vue自定義指令,存在著bindinsertedupdate等鉤子函式,其中bindunbind與元素的display息息相關,這樣滿足了表單掛載、登出的語義,且只觸發一次;
    • 建構函式提供了大量的引數,可以方便與元件通訊,如elbind等。

    我們利用指令可以傳遞的值argmodeifiersvalue,傳遞引數,其中為了方便校驗規則與資料的結合,我們利用箭頭函式將校驗函式繫結在當前context內,如下程式碼

    nativeInput: [{
        // need function context = dangqian zujian 
        need: () => !!this.form.nativeInput,
        warn: '不能為空'
    }]
    複製程式碼

    將上述資料傳遞給Handler,並且為了控制校驗的先後順序,將驗證規則定義為Array,這樣就會只獲取第一個報錯資訊;

  • 解析校驗指令,並將資料傳遞至Handler:

    該自定義指令的註冊以及解析如下

    Vue.directive('validate', {
        bind (element, binding, vnode) {
         // 傳給引數arg => 作為校驗結果中的key
         // 指令繫結值value => 作為校驗規則
         // 修飾符modifiers => 作為出發校驗的event型別
        const {
             arg: name,
             modifiers,
             value: rules
        } = binding
        const method = Object.keys(modifiers)[0]
    },
    
        unbind (element, binding) {
            // 註冊
            const { arg: name, modifiers } = binding
            const method = Object.keys(modifiers)[0]
        }
    })
    複製程式碼

    上述code解釋了,如何將rules傳入指令中,接下來介紹如何處理這些規則,當完成元件上v-validate的解析時,就會在eventHandler中訂閱一個校驗物件。

    export default function (Vue) {
        Vue.directive('validate', {
            bind (element, binding, vnode) {
                //... pre code
                // 獲取當前元件的context
                context = vnode.context
                // 將eventHandler$$1繫結在當前Form的context
                // 防止動態切換時無法與元件通訊
                if (eventHandler$$1) {
                    eventHandler$$1.bind(context)
                } else {
                    eventHandler$$1 = new eventHandler(context)
                }
                // 如果method存在的話,當event出發時的處理函式
                const handler = function () {
                    eventHandler$$1.broadcast(name)
                }
                // 在hanler中訂閱一個規則處理物件
                eventHandler$$1.subscribe({
                    name,
                    method,
                    rules,
                    element,
                    handler
                })
    
                method && on(element, method, handler)
            },
    
            unbind (element, binding) {
                //... pre code
                // 當Form登出時,垃圾處理
                const handler = eventHandler$$1.removeSubscribe(name)
                method && off(element, method, handler)
            }
        })
    }
    複製程式碼
  • eventHandler內維護了subscribers用於儲存rule function以及相應的warn info

    export default class eventHandler {
        // 建構函式
        constructor (context) {
            this.context = context
            this.subscribers = {}
        }
        // 動態切換
        bind (context) {
            this.context = context
        }
        // 訂閱,將其維護在eventHandler的subscribers內
        subscribe (options) {
            const { name } = options
            this.subscribers[name] = Object.assign({}, options)
        }
        // 當校驗事件觸發時,進行校驗
        broadcast (name) {
            const { context, subscribers } = this
            const { rules } = subscribers[name]
            // 校驗結果
            const error = findFailRule(rules)
            
            context.$forceUpdate()      
            return error.success
        }
    }
    複製程式碼

    當需要校驗時就會觸發broadcast,獲取相關的rule fucntion來校驗表單,核心是findFailRule function,遍歷rules獲取不符合的規則。由於我們將規則定義為Array,只需要find不符合的規則即可,具體程式碼如下

    findFailRule = (value = {}, rules) => {
        let failRule = null
        if (Array.isArray(rules)) {
            failRule = rules.find(item => {
                return !item.need(value)
            })
        }
        return {
            warn: failRule ? failRule.warn : '',
            success: !failRule
        }
    }
    複製程式碼

    最後維護一個validateResult物件,通過mixins注入到Form元件內,將校驗結果傳遞給Form元件,其具體定義如下

    const validateResult = {
        data () {
            return {
                errors: {
                    get (param) {
                        return this[param]
                            ? this[param]
                            : {
                                warn: '',
                                success: true
                            }
                    }
                }
            }
        }   
    }
    複製程式碼

    在之前,我們將eventHandler繫結在當前context內,這樣可以獲取到注入的errors狀態,當校驗完成時,可將報錯資訊更新至error中,再利用context.$forceUpdate()進行更新。

以上是一個簡單的Vue表單驗證外掛的雛形,為了方便開發業務,還需它具備以下API:

  • validate: 驗證指定規則;
  • validateAll: 驗證所有規則 => 通常用於判斷Form元件是否可以可提交;
  • reset: 重置驗證結果 => 例如input一般要求聚焦重置;
  • resetAll: 重置所有驗證結果。 為了方便引入,利用Vue.use()將上述自定義指令封裝程Vue的外掛。

4. 結論

基於上述外掛,可以自定義函式規則,只需要滿足return一個Boolean值即可。將業務中,常用的驗證規則進行整理、封裝,最後調教出一個聽話的驗證外掛。

PS:有兩點坑:

  • 由於全域性維護了處理中心,所以同一個Form內,name不能相同;
  • 須將驗證函式繫結在data內部,所以不能使用data: () => ({})

原創宣告: 該文章為原創文章,轉載請註明出處。

相關文章