通用表单组件 收集数据、校验数据并提交。

 实现以下组件

 

 

 

 组件设计

原则是高内聚,低耦合; 组件的功能高度专一,这样可以使得组件使用变得灵活,组件之间的耦合度也会随之减小,从而可以提高组件的复用性。

 

 

 Input.vue 组件

对着elmentui 简单的实现了一些功能,此段代码有一个问题是  this.$parent.$emit('validate') 触发校验是,this.$parent 会增加耦合度。

<template>
    <div class="input-box">
        <!-- 前置内容 -->
        <input
        :value="value"
        @input="handleInput"
        v-bind="$attrs"
        class="minput"
        :class="[
            inputSize() ? 'm-input--' + inputSize() : ''
        ]"/>

        <!-- 后置内容 -->
        <span v-if="clearable" @click="clear">x</span>
    </div>
</template>

<script>
import emitter from '@/mixins/emitter.js'
export default {
    inheritAttrs: false, // 此处设置禁用继承特性
    mixins: [ emitter ],
    props: {
        value: { //
            type: String,
            default: ''
        },
        clearable: { // 清空文本域
            type: Boolean,
            default: false
        },
        size: { // 字体大小 big mini
            type: String,
            default: ''
        },
        validateEvent: { // oninput 是否触发校验
            type: Boolean,
            default: true
        }
    },
    methods: {
        handleInput(e) {
            // 派发一个 input 事件
            this.$emit('input', e.target.value)
            // 执行校验
            this.$parent.$emit('validate')
        },
        clear() {
            this.$emit('input', '')
        },
        inputSize() { // input 大小
            return this.size
        }
    }
};
</script>

<style  scoped>
    .input-box {
        width: 180px;position: relative;
    }
    /* default */
    .minput {
        box-sizing: border-box;
        width: 100%;
        height: 35px;
        line-height: 35px;
        padding: 5px  15px;
        color: #666;
        font-size: 13px;
    }
    span {
        display: inline-block;
        position:absolute;
        right: 7px;
        top: 50%;
        transform: translateY(-50%);
        width: 10px;
        height:10px;
        cursor:pointer;
        border: 1px solid #999;
        border-radius: 50%;
        font-size:12px;
        line-height: 0.9;
    }
    .m-input--big {
        height: 50px;
        line-height: 50px;
    }
    .m-input--mini {
        height: 30px;
        line-height: 30px;
    }
</style>

 

 

 

  FormItem.vue 组件

<template>
    <div class="form-item" prop:="name">
        <label v-if="lable">{{ lable }}</label>
        <slot></slot>
        <div v-if="isErr" class="errInfo">{{ isErr }}</div>
    </div>
</template>

<script>
export default {
    inject: ['mForm'],
    props: {
        lable: {
            type: String,
            default: ''
        },
        prop: String
    },
    data() {
        return {
            isErr:''
        };
    },
    mounted() {
        this.$on('validate', this.validate)
    },
    methods: {
        validate() { // 规则校验
            // 规则
            const rules = this.mForm.rules[this.prop]
            // input 值
            const value = this.mForm.model[this.prop]
            // 校验
            rules.forEach(rule => {
                if(rule.required) { // 不为空
                    this.isErr  = value === '' ?  rule.message : ''
                }
            })

            if(!this.isErr) return true
        }
    }
};
</script>

<style scoped>
    .form-item{
        display:flex;
        width: 400px;
        line-height: 35px;
    }
    .errInfo { color: brown}
</style>

 

Form.vue 组件

为了让所有子组件都能访问到自身属性这里 inject了自己。

这里有个问题是 校验所有规则的时候 使用了 this.$children, 会增加组件之间的耦合度。

<template>
    <form>
        <slot></slot>
    </form>
</template>

<script>
export default {
    name: 'myForm',
    provide() {
        return {
            mForm: this
        }
    },
    props: {
        rules: {
            type: Object
        },
        model: {
            type: Object
        }
    },
    data() {
        return {

        };
    },
    methods: {
        validate(callback) {
            const task = this.$children
                .filter(item => item.prop)
                .map(item => item.validate())
                .every(item => item)

            callback(task) // 执行回调
        }
    }
};
</script>

 

View.vue 组件 

这里 this.$alert 弹窗组件下一小节再说

<template>
    <div>
        <Form
            :model="userInfo" :rules="rules" ref="loginForm">
            <FormItem lable="用户名:" prop="name" ref="formItem">
                <Minput
                    v-model="userInfo.name"
                    type="textarea"
                    name="hong"
                    size="big"
                    clearable
                    maxlength="10"
                    placeholder="请输入帐号"/>
            </FormItem>
            <FormItem lable="密码:" prop="pwd">
                <Minput
                    v-model="userInfo.pwd"
                    type="password"
                    maxlength="10"
                    placeholder="请输入密码"/>
            </FormItem>
            <FormItem><button @click.prevent="submit">登录</button></FormItem>
        </Form>
    </div>
</template>

<script>
import Minput from './src/Minput.vue'
import FormItem from './src/FormItem.vue'
import Form from './src/Form.vue'
export default {
    data() {
        return {
            userInfo: {
                name: '123',
                pwd: '123'
            },
            rules: {
                name: [
                    { required: true, message: '请输入活动名称', trigger: 'blur' }
                ],
                pwd: [
                    { required: true, message: '请输入密码', trigger: 'blur' }
                ]
            }
        };
    },
    components: { Minput, FormItem, Form },
    methods: {
        submit() {
            this.$refs['loginForm'].validate((valid) => {
                if(valid) {
                    this.$alert({
                        title: '系统消息',
                        content: '成功登录,哈喽,xxx',
                        duration: 2500,
                        showClose: true
                    })
                }
            })
        }
    }
};
</script>

<style scoped>

</style>

 

下面解决上面提到的两个耦合度的问题

在 elmentui 源码中发现它有更好的解决方法, 使用了 emitter.js 以混入(mixins)的方式

eimtter.js 

里面有两个方法 一个 dispath 向上遍历父级组件,由于父级组件是单链的形式向上遍历,所以这种形式性能消耗比较小。

此方法主要是传入了组件名,和事件名,当遍历到父组件有此组件名则会,保存此父组件,让该父组件向自身派发事件 

parent.$emit.apply(parent, [eventName, ...params])
/**
         * @description 向上遍历 父组件 并派发事件
         * @param { String } componentName 组件名
         * @param { String } eventName 派发事件名
         * @param { Array } params 参数
         */
        dispath(componentName, eventName, params) {
            var parent = this.$parent || this.$root
            var name = parent.$options.componentName

            while(parent && (!name || name != componentName)){
                parent = parent.$parent
                if(parent) name = parent.$options.componentName
            }

            if(parent) parent.$emit.apply(parent, [eventName, ...params])
        },

第二个方法是广播 broadcast

该方法是向下以树的形式遍历,消耗性能较大。实现事件派发的形式和 dispath 差不多,都是自己给自己派发事件

 

// 注意: 遍历组件,会有性能消耗
// 广播
function broadcast(componentName, eventName, params) {
    this.$children.forEach(child => {
        var name = child.$options.componentName

        if(name === componentName) {
            child.$emit.apply(child, [eventName].concat(params))
        } else {
            broadcast.apply(child, [componentName, eventName].concat([params])); // 递归
        }
    })
}

export default {
    methods: {
        /**
         * @description 向上遍历 父组件 并派发事件
         * @param { String } componentName 组件名
         * @param { String } eventName 派发事件名
         * @param { Array } params 参数
         */
        dispath(componentName, eventName, params) {
            var parent = this.$parent || this.$root
            var name = parent.$options.componentName

            while(parent && (!name || name != componentName)){
                parent = parent.$parent
                if(parent) name = parent.$options.componentName
            }

            if(parent) parent.$emit.apply(parent, [eventName, ...params])
        },
        // 向下派发
        broadcast(componentName, eventName, params) {
            broadcast.call(this, componentName, eventName, params)
        }
    }
}

 

解决 Input 的耦合

dispah 遍历父组件

 

 解决 form 的耦合

由于广播会使得性能消耗较大,这里就不使用这种方法了。

当点击提交form 表单事件时,form 表单会校验所有需要校验的 form-item,需要校验的 form-item 会传入 prop 校验名称,因此之前使用 this.$children 的方法获取所有的 form-item 是否有 prop。

现在为了解耦,改变了思路,当 form-item 注册的时候向调用 dispath 派发事件,其实是父级给自己传事件,并传入自身(子组件)

form-item.vue

 

 from.vue

 

 至此组件之间的解耦成功

posted on 2022-04-02 16:06  京鸿一瞥  阅读(58)  评论(0编辑  收藏  举报