前言

很久没有写文章了,学习了一下webpack,基础的一些组件,今天带来form表单验证组件(element.iviewui)的一期教程(作为一个菜鸡毕竟经历众多项目可以给一些新手一点提示 (QQ群技术讨论)838293023备注(github进来的

游泳健身了解一下:github 技术文档 技术文档会持续更新


内容总结

  1. 原理解释
  2. 派发和广播 // 父子级交互
  3. 引入 async-validator // 表单验证插件
  4. form/以及api设计
  5. form-item
  6. input

效果图

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就不解释了

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>

复制代码

小结

可能现在小伙伴还是不懂。。俗话说;师傅领进门,修行在个人。代码上的备注写的也够多了。还是不懂的可以加群问问小伙伴们,

Logo

前往低代码交流专区

更多推荐