工程諺語:若是它沒壞,就不要動它。html
Published: 2019-03-19vue
以前在開發中後臺業務時候,基於 Vue 寫了一個表單驗證的插件,因爲時間比較急,再加上看過的源碼比較少,就草草的實現了。過年期間看了 Vuex 以及 Vue-router 的源碼,對插件的實現有了必定的瞭解,再加上年後公司在裁人,業務有些停滯了,因此抽了兩天把它重構一下,也就應了標題的從0.1開發。node
重構以前,heregit
緣由:github
eventHandler
,用於管理校驗規則與結果,卻沒有進行很好的管理;v-validat
未來傳遞校驗規則,實現方式繁瑣,且全部狀態結果都耦合在組件的 data
中,可是其龐大、不易維護;context.$forceUpdate()
,引入髒檢測,致使總體效率偏低;重構以後的結構:element-ui
v-validate
指令,只是作爲介質,傳遞校驗的 action、rule。本章用例,here設計模式
首先在全局安裝插件app
import validator from "fat-validator";
Vue.use(validator);
複製代碼
以後以 element-ui 的組件庫爲例,建立一個表單ide
<template>
<div class="mock" v-if="isVisible">
<div class="form-wrapper">
<i class="el-icon-close close-btn" @click.stop="close"></i>
<div class="header">
<span>用戶註冊</span>
<span></span>
</div>
<div class="content">
<form-item title="姓名" info="姓名不能修改" :warn="validateResult.name">
<el-input
placeholder="請輸入內容"
v-model="name"
v-validate.input="'name'"
@change="handleChange"
/>
</form-item>
</div>
<div class="footer">
<el-button type="primary" @click="handleClick({ type: 'confirm' })"
>肯定</el-button
>
<el-button type="primary" @click="handleClick({ type: 'reset' })"
>重置姓名</el-button
>
</div>
</div>
</div>
</template>
<script>
import popupWin from "./popup-win.js";
import formItem from "../components/form-item";
// 引入mixin的組件validatorMixin
import { validatorMixin } from "fat-validator";
export default {
mixins: [popupWin, validatorMixin],
components: {
formItem
},
data() {
return {
name: ""
};
},
validator() {
return {
name: [
{
need: () => !!this.name,
warn: "不能爲空"
},
{
need: () => this.name.length <= 20,
warn: "不能超過20個字符"
}
]
};
},
methods: {
handleClick({ type }) {
const handler = {
reset: () => this.$validator.reset("name"),
confirm: () => {
if (this.$validator.validateAll()) {
this.$emit("done", name);
}
}
};
handler[type] && handler[type]();
},
handleChange() {
this.$validator.validate("name");
}
}
};
</script>
複製代碼
利用 v-validate.input="'name'"
,在組件上綁定指令,其中 input 表明校驗觸發時,所須要的事件,'name' 表明所屬的校驗規則組件化
validator() {
return {
name: [
{
need: () => !!this.name,
warn: "不能爲空"
},
{
need: () => this.name.length <= 20,
warn: "不能超過20個字符"
}
]
};
}
複製代碼
同時默認添加狀態 validateResult.name
表明校驗的結果
this.$validator
能夠調用四個方法:
validate
用於驗證單個規則,參數是key,例如上述 v-validate.input="'name'"
, 能夠寫爲 @input="$validator.validate('name')"
reset
用於重置某個驗證結果,例如要重置上述驗證結果this.$validator.reset('name')
validateAll
用於驗證全部規則,例如this.$validator.validateAll()
resetAll
用於重置全部規則,例如 this.$validator.resetAll()
。本章代碼,here
首先利用 mixins
對錶單進行擴展,將 validatorMixin
注入到表單組件中,主要完成兩個任務
其一:將 validateResult
mixin 到組件中,方便組件利用校驗結果來展現不一樣信息;
const validatorMixin = {
data() {
return {
validateResult: {},
}
}
...
}
複製代碼
其二:對組件擴展,添加 $validator
對象,用於實現 validate
、reset
、validateAll
等方法;
beforeCreate() {
if (isDef(this.$options.validator)) {
const { _uid, $options: { validator } } = this
const _validator = validator.call(this)
...
this.$validator = _validator
}
}
複製代碼
主要對第二點進行下介紹,將validatorMixin
mixin 到組件中
// validatorMixin
{
data() {
return {
validateResult: {},
}
},
beforeCreate() {
if (isDef(this.$options.validator)) {
const { _uid, $options: { validator } } = this
const _validator = validator.call(this)
...
this.$validator = _validator
}
},
}
// 組件
{
mixins: [validatorMixin],
data() {
return {
name: ""
};
},
validator() {
return {
name: [{
need: () => !!this.name,
warn: "不能爲空"
},
{
need: () => this.name.length <= 20,
warn: "不能超過20個字符"
}
]
};
},
}
複製代碼
在 beforeCreate
生命週期中,對組件的 $options
進行訪問,獲取到當前組件的 _uid
、自定義的 validator
利用 validator.call(this)
,將 validator
的 context 綁定在當前組件中,這樣方便後續利用 this
指針來獲取當前組件的 data
,簡化驗證規則。
const _validator = validator.call(this)
// 定義propConfig,防止方法被 enum 以及 write
const propConfig = {
writable: false,
enumerable: false,
}
// init
Object.keys(_validator).forEach((key) => {
this.$nextTick(() => {
this.$set(this.validateResult, key, '')
})
})
複製代碼
以後利用 this.$set
對以前 mixin validateResult
對象進行初始化,使得每一個校驗結果都變爲響應式。
**PS:**爲何利用 this.$nextTick
,是要在 mixin 、組件化完成再對 validateResult 進行修改。
Object.defineProperties(_validator, {
validate: {
value(key) {
validatorEmmiter.emit(`${_uid}-${key}`)
},
...propConfig,
},
reset: {
value: (key) => {
this.validateResult[key] = ''
},
...propConfig,
},
validateAll: {
value: () => {
Object.keys(_validator).forEach((key) => {
const haveListeners = (eventName) =>
validatorEmmiter.listenerCount(eventName)
if (haveListeners(`${_uid}-${key}`)) {
validatorEmmiter.emit(`${_uid}-${key}`)
}
})
return Object.keys(this.validateResult).every(
(item) => this.validateResult[item] === ''
)
},
...propConfig,
},
resetAll: {
value: () => {
Object.keys(_validator).forEach((key) => {
this.validateResult[key] = ''
})
},
...propConfig,
},
})
複製代碼
以後利用 Object.defineProperties
對 _validator
進行擴展,添加 validate
、reset
、validateAll
等方法,每一個方法的邏輯都比較簡單,其中 validatorEmmiter
是用來管理校驗Action'的,接下後詳細介紹。
import events from 'events'
class ValidatorEmmiter extends events {
constructor() {
super()
}
}
const validatorEmmiter = new ValidatorEmmiter()
validatorEmmiter.setMaxListeners(100)
複製代碼
validatorEmmiter
的實現,特別簡易,利用了 node 的 events 模塊
class ValidatorEmmiter extends events:爲何要存在,方便後續對 ValidatorEmmiter
進行擴展,管理事件。
上述簡單介紹了注入組件的校驗結果模塊,接下介紹如何傳遞校驗規則、校驗Action,與以前一致的是,依然利用指令 v-validate
傳遞校驗規則
具體形式如 v-validate.input="'name'"
,表明着組件觸發 input 事件時候進行校驗,校驗規則爲 name
{
install(Vue) {
const eventHandler = {}
Vue.directive('validate', {
bind(el, binding, vnode) {
const { modifiers, value: key } = binding
const { context: { _uid } } = vnode
const method = Object.keys(modifiers)[0]
...
},
...unbind
})
},
}
複製代碼
具體API見 Vue插件,利用參數 (el, binding, vnode)
獲取上述的組件的 _uid
,校驗規則的 key
,校驗的Action method
以後利用 validatorEmmiter
進行訂閱與發佈,具體時機爲
bind
時,也就是組件 render 時,validatorEmmiter
// on
validatorEmmiter.on(`${_uid}-${key}`, () => {
const { context: { validateResult, $validator } } = vnode
// 找到不知足的 rule
const result = $validator[key].find((item) => !item.need())
validateResult[key] = isDef(result) ? result.warn : ''
})
// emit
if (method) {
eventHandler[`${_uid}-${key}`] = () => {
validatorEmmiter.emit(`${_uid}-${key}`)
}
// 用戶監聽組件的事件,來emit對應的規則
vnode.componentInstance.$on(
method,
eventHandler[`${_uid}-${key}`]
)
}
複製代碼
unbind
,須要對已監聽的事件進行 remove
以及 $off
unbind: function(el, binding, vnode) {
const { modifiers, value: key } = binding
const { context: { _uid, $validator } } = vnode
const method = Object.keys(modifiers)[0]
// reset & remove event
$validator.reset(key)
validatorEmmiter.removeAllListeners(`${_uid}-${key}`)
if (method) {
vnode.componentInstance.$off(
method,
eventHandler[`${_uid}-${key}`]
)
}
},
複製代碼
這篇主要是用來總結以前重構知識的吧,還有就是看了一些源碼,總要有產出的吧。