前言
在日常开发中,如果遇到当前提供的组件不足以完成现在的功能,但是当前的组件已经有完整地代码或者是UI组件库 (elementUI、vantUI) 提供的成套组件,使我们不能对源码进行修改,那就要二次封装或许是一个很好的方案!!!
什么是二次封装
我们可以把children组件当做最底层的组件,但是children组件不能满足最新的需求,那么在允许不修改children组件的前提下,就可以在children组件外面套一层parent组件,那么这就是组件的二次封装。
预热尝鲜
首先用最简单的方式实现一下上面图的功能!!
// root组件
<template>
<parent :a="a" b="b" :c="c"></parent>
</template>
<script>
import parent from "./parent.vue";
export default {
components: { parent },
data() {
return {
a: 1,
b: 2,
c: 3,
};
},
};
</script>
// parent组件
<template>
<div>
<h3>我是来自root组件的数据a==>{{ a }}</h3>
<children b="b" :c="c"></children>
</div>
</template>
<script>
import children from "./children.vue";
export default {
components: { children },
props: ["a", "b", "c"],
};
</script>
// children组件
<template>
<div>
<h5>我是来自root组件的数据b==>{{ b }}</h5>
<h5>我是来自root组件的数据c==>{{ c }}</h5>
</div>
</template>
<script>
export default {
props: ["b", "c"],
};
</script>
上面的代码理论上实现了二次封装的效果,如果以后还需children组件进行扩充,完全可以在parent组件上面进行修改。
parent组件组件会接受来自root组件的所以数据,做一个中转站传递给children组件,这里明显可以发现虽然parent组件没有使用b和c,但还是需要在props接受,这数据量小还是可以接受的。
但是这里提出一个问题,如果children组件是elementUI的内置组件,在开发的过程中根本不知道children组件所需的参数喃?有的人就会说喃,怎么可能不知道喃,完全可以通过查看文档和源码来确定呀,那如果children组件需要100多个参数,我们也要去用parent组件做为数据的中转吗?想想是不是很繁琐荒唐。
$attrs的妙用
$attrs到底是什么
首先看一下来自官方的八股文
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
还是使用上面的代码例子,但是要稍微改动一下parent组件
// root组件
<template>
<parent :a="a" b="b" :c="c"></parent>
</template>
<script>
import parent from "./parent.vue";
export default {
components: { parent },
data() {
return {
a: 1,
b: 2,
c: 3,
};
},
};
</script>
<template>
<div>
<h3>我是来自root组件的数据a==>{{ a }}</h3>
<h1>我是parent组件的$attrs==>{{ $attrs }}</h1>
</div>
</template>
<script>
export default {
props: ["a"], //props只接受本组件需要的参数
};
</script>
可以发现parent组件改成了props只接受本组件需要的参数;打印
$attrs发现输出了一个对象,对象键值对包含了parent组件的props并没有接受的b和c参数,那这里也可以简单的理解一下;$attrs值就是一个对象,里面的键值对就是当前组件props并没有接受的参数,当一个组件没有声明任何 prop 时,$attrs就会包含所有父级组件传递的参数 (class 和 style 除外),现在再去结合官网的八股文,大概就能猜到什么意思了吧。
v-bind的特殊使用
这里就简单补充一下这个知识,还是使用上面的例子
// root组件
<template>
<!-- <parent :a="a" b="b" :c="c"></parent> -->
<parent v-bind="{ a: a, b: b, c: c }"></parent>
</template>
<script>
import parent from "./parent.vue";
export default {
components: { parent },
data() {
return {
a: 1,
b: 2,
c: 3,
};
},
};
</script>
root组件通过v-bind传值所达到的效果和attribute是一样,区别就是v-bind接受可以一个对象,并且把对象拆除单独的参数去传递给子组件
使用$attrs实现二次封装
学习了上面的知识,那么我就可以如果$attrs配合v-bind来实现更加完善的组件二次封装
// root组件
<template>
<parent :a="a" b="b" :c="c"></parent>
</template>
<script>
import parent from "./parent.vue";
export default {
components: { parent },
data() {
return {
a: 1,
b: 2,
c: 3,
};
},
};
</script>
// parent组件
<template>
<div>
<h3>我是来自root组件的数据a==>{{ a }}</h3>
<children v-bind="$atts"></children>
</div>
</template>
<script>
import children from "./children.vue";
export default {
components: { children },
props: ["a"],
};
</script>
// children组件
<template>
<div>
<h5>我是来自root组件的数据b==>{{ b }}</h5>
<h5>我是来自root组件的数据c==>{{ c }}</h5>
</div>
</template>
<script>
export default {
props: ["b", "c"],
};
</script>
只需改动parent组件就可以拉,回顾一下我们采用第一种方案,如果children组件需要参数,还需要parent组件进行传递,但是在使用$attrs就完全不用考虑这些问题了,我们可以在root组件传递,children组件可以直接接收到,完全不用parent组件进行传递
结语
其实除了$attrs和v-bind配合传值外,还有$listeners和v-on传递事件函数,两者原理和解决的问题也是相似的