1.封装Vue组件的一些技巧(5000)第一篇

209 阅读1分钟

Vue的组件系统

前言

全局组件
组件设计的第一步是就是先要思考组件类型,(展示组件,业务组件,容器组件)
考虑设计组件用例
设计用例主要就是设计,
组件的调用方式(组件,api)<button />  || this.$alert('xxxx')
  考虑可能需要的属性   props
  考虑事件(v-on)
  子元素,需要接受的子元素  slot
  方法,扩展的方法,抛出的接口  可以供组件使用者调用的方法

Vue组件的API主要包含三部分:prop、event、slot

1.props 表示组件接收的参数,最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,此外还可以通过type、validator等方式对输入进行验证

2.slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径;当需要多个插槽时,可以使用具名slot

3.event是子组件向父组件传递消息的重要途径

单向数据流

单向数据流是Vue组件一个非常明显的特征,不应该在子组件中直接修改props的值

如果传递的prop仅仅用作展示,不涉及修改,则在模板中直接使用即可

如果需要对prop的值进行转化然后展示,则应该使用computed计算属性

如果prop的值用作初始化,应该定义一个子组件的data属性并将prop作为其初始值

在子组件修改props,却不会修改父组件,这是因为extractPropsFromVNodeData中是通过浅复制将attrs传递给props的。

浅复制意味着在子组件中对对象和数组的props进行修改还是会影响父组件,这就违背了单向数据流的设计。
因此需要避免这种情况出现。

组件之间的通信

组件通信就是两个或两个以上的组件数据进行传递,称为组件通信,而组件通信有这五种场景
父子
    props传递,
    子组件通过$pa ren获取父组件实例拿到数据,推荐使用props
子父
    父组件使用子组件的数据
    1,自定义事件,在子组中触发事件传递数据,父亲组件接受
    2, $children获取子 组件实例
    3, $refs 获取子组件实例
兄弟
    1.在兄弟1this.$parent.$emit(事件名,参数)
    2.在兄弟2生命周期里面this.$parent.$on(事件名,事件处理函数)
祖先和后代(跨级)
    1.在祖先用provider来提供,
    2.后代里面用inject接收属性名
任意组件通信
    1.在main.js里面vue.prototype.$bus=new vue()
    2.发送this.$bus.$emit(事件名,参数)
    3.created生命周期接收this.$bus.$on(事件名,事件处理函数)

“绕开”单向数据流

考虑下面场景:父组件将数据通过prop形式传递给子组件,子组件进行相关操作并修改数据,需要修改父组件的prop值(一个典型的例子是:购物车的商品数量counter组件)。
根据组件单向数据流和和事件通信机制,需要由子组件通过事件通知父组件,并在父组件中修改原始的prop数据,完成状态的更新。
在子组件中修改父组件的数据的场景在业务中也是比较常见的,用v-model可以解决

v-model

v-model本质上就是一个语法糖,实现原理其实就是上面说过的数据绑定加上底层的input事件监听,通过v-bind绑定一个数据传给子组件,子组件里面的model默认用value属性接受,然后子组件监听数据发生变化,emit触发父组件的input事件,通过触发事件来进行传值,实现了父子组件数据的双向绑定。

获得组件实例的引用

在开发组件中,获取组件实例是一个非常有用的方法。组件可以通过$refs、$parents、$children等方式获得vm实例引用
$refs在组件(或者dom上)增加ref属性即可
$parents获取子组件挂载的父组件节点
$children,获取组件的所有子节点

表单验证组件

通常情况下,表单验证是表单提交前一个十分常见的应用场景。那么,如何把表单验证的功能封装在组件内部呢?
下面是一个表单组件的示例,展示了通过获得组件的引用来实现表单验证功能。
首先定义组件的使用方式,
form接收model和rule两个prop
model表示表单绑定的数据对象,最后表单提交的就是这个对象
rule表示验证规则策略,表单验证可以使用async-validator插件
form-item接收的prop属性,对应form组件的model和rule的某个key值,根据该key从model上取表单数据,从rule上取验证规则

<template>
<div class="page">
    <form :model="form" :rule="rule" ref="baseForm">
        <form-item label="姓名" prop="name">
            <input v-model="form.name"/>
        </form-item>
        <form-item label="邮箱" prop="email">
            <input v-model="form.email"/>
        </form-item>
        <form-item>
            <button @click="submit">提交</button>
        </form-item>
    </form>
</div>
接下来让我们实现form-item组件,其主要作用是放置表单元素,及展示错误信息
<template>
    <label class="form-item">
        <div class="form-item_label">{{label}}</div>
        <div class="form-item_mn">
            <slot></slot>
        </div>
        <div class="form-item_error" v-if="errorMsg">{{errorMsg}}</div>
    </label>
</template>
<script>
    export default {
        name: "form-item",
        props: {
            label: String,
            prop: String
        },
        data() {
            return {
                errorMsg: ""
            }
        },
        methods: {
            showError(msg) {
                this.errorMsg = msg
            }
        }
    }
</script>
通过FormItems获取每个form-item的引用,保存在formItems中
暴露validate接口,内部调用AsyncValidator,并根据结果遍历formItems中每个表单元素的prop属性,处理对应的error信息
        <template>
    <div class="form">
        <slot></slot>
    </div>
</template>

<script>
    import AsyncValidator from 'async-validator';

    export default {
        name: "form",
        props: {
            model: {
                type: Object
            },
            rule: {
                type: Object,
                default: {}
            }
        },
        data() {
            return {
                formItems: []
            }
        },
        mounted() {
            this.calcFormItems()
        },
        updated() {
            this.calcFormItems()
        },
        methods: {
            calcFormItems() {
                // 获取form-item的引用
                if (this.$slots.default) {
                    let children = this.$slots.default.filter(vnode => {
                        return vnode.tag &&
                            vnode.componentOptions && vnode.componentOptions.Ctor.options.name === 'form-item'
                    }).map(({componentInstance}) => componentInstance)

                    if (!(children.length === this.formItems.length && children.every((pane, index) => pane === this.formItems[index]))) {
                        this.formItems = children
                    }
                }
            },
            validate() {
                let validator = new AsyncValidator(this.rule);

                let isSuccess = true

                let findErrorByProp = (errors, prop) => {
                    return errors.find((error) => {
                        return error.field === prop
                    }) || ""
                }

                validator.validate(this.model, (errors, fields) => {
                    this.formItems.forEach(formItem => {
                        let prop = formItem.prop
                        let error = findErrorByProp(errors || [], prop)
                        if (error) {
                            isSuccess = false
                        }

                        formItem.showError(error && error.message || "")
                    })
                });

                return Promise.resolve(isSuccess)
            }
        }
    }
</script>
到这里我们就完成了一个表单验证的组件

总结

文章整理了几种实现Vue组件的技巧
以counter计数器组件为例子,展示了通过v-model语法糖父子组件的绑定
以表单验证组件为例子,展示了通过获取子组件的实例来封装组件的方法
关于封装组件库知识了解的还不是太深入,需要多多的学习和积累,才能让自己不断的进步,加油