上一篇写到了三万多字,打字都要延迟好一会,现在能实时显示文字,感觉好丝滑哈哈。
目录
12.16.3.defineProps()和defineEmits()
如果有什么不懂的可以查Vue.js官网
8.Vue组件化 - 父子组件间通信
- 在之前的案例中,我们只是创建了一个组件App;
- 如果我们一个应用程序将所有的逻辑都放在一个组件中,那么这个组件就会变成非常的臃肿和难以维护;
- 所以组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件;
- 再将这些组件组合嵌套在一起,最终形成我们的应用程序;
在真实开发中,我们可以将项目进行拆分。
- App组件是Header、Main、Footer组件的父组件;
- Main组件是Banner、ProductList组件的父组件;
- 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
- 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给它们来进行展示;
- 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;
父子组件之间传递是很重要的一个通信方式。
那么父子组件之间如何进行通信呢?
- 父组件传递给子组件:通过props属性;
- 子组件传递给父组件:通过$emit触发事件。
8.1.父组件传递给子组件
在开发中很常见的就是父子组件之间通信,比如父组件有一些数据,需要子组件来进行展示:这个时候我们可以通过props来完成组件之间的通信;
- Props是你可以在组件上注册一些自定义的attribute;
- 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值;
<!-- App.vue -->
<template>
<cpn name="zzz" height="1.7" age="18"></cpn>
</template>
<script>
import cpn from './components/cpn.vue'
export default {
data() {
return {
}
},
components: {
cpn:cpn
}
}
</script>
<style scoped>
</style>
- 在App.vue父组件文件中,conponents对象定义了一个组件cpn;
- 又用import引入组件cpn文件的位置,在template里面使用cpn标签;
- 在cpn标签中填要传入子组件的属性与值,在子组件cpn.vue接收。
<!-- cpn.vue -->
<template>
<div class="infos">
<h2>姓名:{
{name}}</h2>
<h2>年龄:{
{age}}</h2>
<h2>身高:{
{height}}</h2>
</div>
</template>
<script>
export default {
props:["name", "age", "height"]
}
</script>
<style scoped>
</style>
- 使用props属性接收传入的attribute属性名称;
- 在template中使用传入的值,如果使用的值发现没有在props属性定义,就会去data找。
Props有两种常见的用法:
- 方式一:字符串数组,数组中的字符串就是attribute的名称;
- 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等;
上面演示使用的是方式一,下面对两种用法进行说明。
8.1.1.props的数组用法
子组件的props接收父组件传递来的attribute,需要一一对应,props中保存字符串数组。
这种数组用法只能说明传入的attribute的名称,不能对其进行任何形式的限制,所以在实际开发中用得更多的是props的对象用法。
8.1.2.props的对象用法
当使用对象语法的时候,我们可以对传入的内容限制更多:
- 指定传入的attribute的类型;
- 指定传入的attribute是否是必传的;
- 指定没有传入时,attribute的默认值。
props:{
name: {
type: String,
required: true,
default: "z"
},
age: {
type: Number,
required: false,
default: 10
},
height: {
type: Number,
required: false,
default: 1.00
}
}
一般attribute不是必传的时候,默认值也不写。
type的类型
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
对象类型的其他写法
props:{
name: String,//基础的类型检测
age: [String, Number],//多个可能的类型
propO:{
type: Object,//对象和数组的默认值必须从一个工厂函数获取
default(){
return { message: "message"}
}
}
}
prop的大小写命名
HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符;
这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名;
8.1.3.attribute继承
非prop的attribute
在了解attribute继承之前需要了解非prop的attribute的概念。
- 当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute;
- 常见的包括class、style、id属性等;
Attribute继承
<!-- 父组件传递了class和id App.vue -->
<template>
<cpn class="cpn" id="cpn1" name="zzz" height="1.7" age="18"></cpn>
</template>
<script>
import cpn from './components/cpn.vue'
export default {
data() {
return {
}
},
components: {
cpn:cpn
}
}
</script>
<style scoped>
</style>
<!-- cpn.vue -->
<template>
<div class="infos">
<h2>姓名:{
{name}}</h2>
<h2>年龄:{
{age}}</h2>
<h2>身高:{
{height}}</h2>
</div>
</template>
<script>
export default {
props:{
name: String,
age: [String, Number],
propO:{
type: Object,
default(){
return { message: "message"}
}
}
}
}
</script>
<style scoped>
</style>
可以看到子组件的根节点继承了父组件传过来的非prop的attribute。
禁用attribute继承
//cpn.vuecpn.vue
export default {
inheritAttrs:false,
}
- 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素;
- 我们可以通过 $attrs来访问所有的 非props的attribute;
<template>
<div class="infos">
<h2 :class="$attrs.class">姓名:{
{name}}</h2>
<h2>年龄:{
{age}}</h2>
<h2>身高:{
{height}}</h2>
</div>
</template>
多个根节点的attribute
多个根节点的attribute如果没有显示的绑定,那么会报警告,我们必须手动的指定要绑定到哪一个属性上。
8.2.子组件传递给父组件
有父组件传递给子组件的情况,也有子组件传递给父组件的情况,
- 比如当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;
- 子组件有一些内容想要传递给父组件的时候;
现编写一个案例:父组件显示计数器的数,子组件负责增加数量按钮。
<!-- cpn.vue 子组件-->
<template>
<div>
<!-- 1.子组件触发事件 -->
<button @click="addCount(1)">+1</button>
<button @click="addCount(5)">+5</button>
<button @click="addCount(10)">+10</button>
</div>
</template>
<script>
export default {
methods: {
// 2.子组件将自定义的事件名add和参数count通过this.$emit()传递出去
addCount(count) {
this.$emit("add", count)
}
}
}
</script>
<style scoped>
</style>
1.子组件触发事件;
2. 子组件将自定义的事件名add和参数count通过this.$emit()传递出去;
<!-- App.vue 父组件 -->
<template>
<div>
<div class="count">当前计数{
{counter}}</div>
<!-- 3.父组件使用自定义的add事件,并且编写方法 -->
<cpn @add="addClick"></cpn>
</div>
</template>
<script>
import cpn from './components/cpn.vue'
export default {
data() {
return {
counter: 0
}
},
components: {
cpn:cpn
},
methods: {
// 4.父组件对自定义事件触发的方法进行处理
addClick(count) {
this.counter += count
}
},
}
</script>
<style scoped>
</style>
3.父组件使用自定义的add事件,并且编写方法;
4.父组件对自定义事件触发的方法进行处理。
8.2.1.emit属性
子组件中可以在属性中先注册传递出去的事件名。父组件在使用自定义事件的时候就会有提示了。
export default {
emits:["add"],
methods: {
// 2.子组件将自定义的事件名add和参数count通过this.$emit()传递出去
addCount(count) {
this.$emit("add", count)
}
}
}
9.Vue组件化 - 插槽Slot
- 前面我们会通过props传递给组件一些数据,让组件来进行展示;
- 但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素;
- 比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片;
- 我们应该让使用者可以决定某一块区域到底存放什么内容和元素;
- 1这个组件分成三块区域:左边-中间-右边,每块区域的内容是不固定;
- 左边区域可能显示一个菜单图标,也可能显示一个返回按钮,可能什么都不显示;
- 中间区域可能显示一个搜索框,也可能是一个列表,也可能是一个标题,等等;
- 右边可能是一个文字,也可能是一个图标,也可能什么都不显示;
这个时候我们就可以来定义插槽slot:
- 插槽的使用过程其实是抽取共性、预留不同;
- 我们会将共同的元素、内容依然在组件内进行封装;
- 同时会将不同的元素使用slot作为占位,让外部决定到底显示什么样的元素;
9.1.slot的基本使用
如何使用slot呢?
- Vue中将 <slot> 元素作为承载分发内容的出口;
- 在封装组件中,使用特殊的元素<slot>就可以为封装组件开启一个插槽;
- 该插槽插入什么内容取决于父组件如何使用;
有点难理解,举个例子就清晰一些了。
App.vue是父组件,slotContent.vue是子组件,slotContent里面放了slot,在App的子组件标签里面添加一些元素默认就添加到了子组件的slot里面。
9.2.slot的默认内容
如果父组件在调用子组件的时候没有往插槽里面添加内容,就会使用子组件slot的默认值。
9.3.多个插槽的效果
如果我们想要做一个顶部菜单栏,就需要把插槽分成三个部分,左部、中部、右部。如果按照上面的写法会有什么样的效果。
会将App.vue的三个元素依次对应到slotContent.vue的slot里面吗?
并不会对应,而是相同的内容展示三次。那要怎么才能对应呢,需要使用具名插槽来实现了。
9.4.具名插槽的使用
- 具名插槽顾名思义就是给插槽起一个名字,<slot> 元素有一个特殊的 attribute:name;
- 一个不带 name 的slot,会带有隐含的名字 default;
<!-- App.vue-->
<template>
<div>
<slot-content title="标题">
<template v-slot:left>
<button >left</button>
</template>
<template v-slot:center>
<h2>center<