前言
很久沒有寫文章了,學習了一下webpack,基礎的一些元件,今天帶來form表單驗證元件(element.iviewui)的一期教程(作為一個菜雞畢竟經歷眾多專案可以給一些新手一點提示 (QQ群技術討論)838293023備註(github進來的
內容總結
- 原理解釋
- 派發和廣播 // 父子級互動
- 引入 async-validator // 表單驗證外掛
- form/以及api設計
- form-item
- input
效果圖
1.原理解釋
考慮
我們看一下我們可以用form去整體觸發校驗也可以單個input來觸發form-item 進行校驗 童鞋們現在可能感覺還是沒懂,沒關係繼續往下看。
2.派發和廣播
為什麼要用廣播和派發呢。通常我們和業務沒有關係的元件儘量不要使用vuex和bus(事件匯流排)。
下面我送上廣播和派發的程式碼。我們在需要呼叫元件綁上this.$on('event',res=>())
,通過派發和廣播進行呼叫$emit
。
- 派發是向上查詢且只呼叫1個
- 派發是向下查詢呼叫多個
- 注意⚠️所有的元件都要寫上
name
- 通過混合器
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
官網的圖`
form
有2個注入的欄位:rules
規則,和:model
當前form的值會通過model
的值和rules
進行匹配來進行校驗.form-item
有2個注入的欄位lable
和prop
(prop
)是來和form
進行匹配來獲取當前的form-item
的值的input
其實有當前的@input
的方法。v-model
就不解釋了
form
- 我們在
form
先開始注入當前所有的form-item
例項(獲取) created
會在生命週期開始的時候繫結和刪除當前例項的方法。通常繫結都在頁面dom開始前呼叫需要在dom
載入完provide
配合inject 使用讓子元件可以呼叫當前父元件的方法以及data- 下面都寫了備註可以放心食用(經過測試當前是可以進行校驗的)
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
form-item
比較複雜我們一個一個講isRequired
來判斷當前是否需要必填validateState
來判斷當前校驗的狀態validateMessage
當前的錯誤的值inject: ['form']
我們就可以通過this.from.xxx
來呼叫父元件的事件以及值了computed
下的fieldValue
可能在不停的變化所以我們通過計算屬性來使用initialValue
預設的值我們在mounted
的時候且當前需要進行校驗的時候(prop
有的時候)會賦值mixins: [Emitter]
混合器就是裡面的方法以及date都可以在當前呼叫使用頻繁的都可以放在混合器裡面- 我們
form-item
會傳入input
的兩個方法blur
和change
(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
value
支援一個入參- 因為當前是一個
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>
複製程式碼
小結
可能現在小夥伴還是不懂。。俗話說;師傅領進門,修行在個人。程式碼上的備註寫的也夠多了。還是不懂的可以加群問問小夥伴們,