- 上面的嵌套逻辑如下,他们存在如下的关系: APP组件是Header、Main、Footer组件的 父组件 Main组件是Banner、ProductList组件的 父组件
- 在开发过程中,我们会经常遇到需要 组件之间相互进行通信
- 比如 App可能使用了多个Header ,每个地方的 Header展示的内容不同 ,那么我们需要使用者 传递给Header一些数据来让其进行展示
- 又比如我们在Main中一次性 请求了Banner数据和ProductList数据 ,那么就需要传递给它们来进行展示
- 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件
- 父子组件之间如何进行通信呢?
- 父组件传递给子组件:通过props属性
- 子组件传递给父组件:通过$emit触发事件
ShowInfo.vue
<template>
<!--问题一:1. 但是如果只是这样写死的话,只能展示一个人的信息 -->
<div class="infos">
<div>身高:188</div>
<div>体重:135</div>
<div>姓名:zql</div>
</div>
</template>
<script>
export default {
// 问题一:2.另外一种想法就是写在data里面,但是data也是相当于给内容写死了
// 所以我们希望的就是,App在用我们组件的时候,数据由App决定
// 父组件传值给子组件
}
</script>
<style scoped>
</style>
- Props是你可以在组件上 注册一些自定义的 attribute
- 父组件给这些attribute赋值,子组件通过attribute的名称来获取到对应的值
- 方式一:字符串数组,数组中的字符串就是attribute的名称
- 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等
第一种方式:数组语法 App.vue
<template>
<show-info name="zql"
age="18"
height="1.88" />
<show-info name="james"
age="37"
height="1.88" />
</template>
<script>
import ShowInfo from './ShowInfo.vue';
export default {
components: {
ShowInfo
}
}
</script>
<style scoped>
</style>
ShowInfo.vue
<template>
<!--问题二: 2.1 这里怎么拿到父组件传入的数据呢? -->
<div class="infos">
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<div>身高:{{height}}</div>
</div>
</template>
<script>
export default {
/*
问题2:2.2 这里如何接收到父组件传过来的值呢?
以前我们这里定义数据是直接写在 data(){}函数中返回的对象,为什么data必须是个对象呢?
因为在创建数据的过程中是相当于根据data构造器来new出来一个对象,数据存储在对象中
这样才能保证没个data中的数据都存在一块独立的空间,如果是对象的话,那么如果两份数据都存在这个对象中
相当于是直接赋值的地址,那么两块数据都存储在同一块空间中,就有可能会造成冲突
我们这里是用一个 props来接收父组件传递过来的属性,这里用一个数组来接收,
这样上面既可以访问 data中的数据,也可以访问 props 中的数据
*/
props: ["name", "age", "height"]
}
</script>
<style scoped>
</style>
第二种方式:对象语法
- 数组语法的弊端
- 不能对数据类型进行验证,比如需要的是数字类型,但是传过来的值却是个字符串
- 没有默认值的,比如我们希望当没有传入数据的时候来展示默认值
App.vue
<template>
<show-info name="zql"
:age="18"
:height="1.88" />
<!-- 传入Number数字类型的时候,只需要这里加上:即可,
加上:就变成了JS代码,JS代码里面写30对应的就是数字类型,如果不写就是字符串
:age="18" 数字 :age="'18'" 字符串
不加:的话就是我们平常写的 name="wc"字符串 加上了:就是变成了vue中的特定语法,后面的就变成数据了
-->
<show-info name="james"
:age="37"
:height="1.88" />
<!-- 不传入值,就会使用默认值 -->
<show-info />
</template>
<script>
import ShowInfo from './ShowInfo.vue';
export default {
components: {
ShowInfo
}
}
</script>
<style scoped>
</style>
ShowInfo.vue
<template>
<div class="infos">
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<div>身高:{{height}}</div>
</div>
</template>
<script>
export default {
/* 3.2第二种:对象语法 但是这种是没有默认值的
props: {
name: String,
age: Number,
height: Number
}
*/
props: {
name: {
type: String,
default: "我是默认的name"
},
age: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
}
}
}
</script>
<style scoped>
</style>
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
<script>
export default {
/* 3.2第二种:对象语法 但是这种是没有默认值的
props: {
name: String,
age: Number,
height: Number
}
*/
props: {
name: {
type: String,
default: "我是默认的name"
},
age: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
},
friend: {
type: Object,
// 这里需要注意的是,如果返回值的类型是对象,那么返回值就是函数
default: () => ({ name: "james" })
}
}
}
</script>
props:{
messageInfo:String,
// 基础的类型检查('null'和'undefined'会通过任何类型验证)
propA:Number,
// 多个可能的类型
propB:[String,Number],
// 必填的字符串
propC:{
type:String,
required:true
},
// 拥有默认值的数字
propD:{
type:Number,
default:100
}
}
// 带有默认值的对象
propE:{
type:Object,
default(){
return {message:'hello'}
}
}
// 箭头函数形式
propF:{
type:Object,
default()=>({name:'james'})
}
// 自定义验证函数
propG:{
validator(value){
// 这个值必须是匹配下列字符串中的一个
return ['success','warning','danger'].includes(value)
}
}
// 具有默认值的函数
propH:{
type:Function,
// 与对象或数组默认值不同,这不是一个工厂函数,这是一个用作默认值的函数
default(){
return 'default function'
}
}
ShowInfo.vue
<script>
export default {
props: {
name: {
type: String,
default: "我是默认的name"
},
age: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
},
// 重要的原则,对象类型写默认值的时候,需要编写default的函数,函数返回默认值
friend: {
type: Object,
// 这里需要注意的是,如果返回值的类型是对象,那么返回值就是函数
default: () => ({ name: "james" })
},
hobbies: {
// 数组类型也是对象
type: Array,
default: () => ["篮球", "rap", "唱跳"]
}
}
}
</script>
- HTML中的attribute名是大小写不敏感的,所以浏览器会把所有大写字符解释成小写字符
- 这意味着当你使用 DOM 中的模板时,camelCase(驼峰命名法)的 prop 名需要使用其等价的kebab-case(短横线分隔命名)命名
- 什么是非Prop的Attribute呢?
- 当我们传递给一个组件的某个属性,但是该属性并没有定义对应的props或者emits时就称之为非Prop的Attribute
- 常见的包括 class、style、id属性等
- attribute继承
- 当组件有单个根节点时,非Prop的Attribute将会自动添加到根节点的Attribute中
- 禁用非Prop的Attribute
- 如果我们不希望组件的根元素继承attribute可以在组件中设置 inheritAttrs:false
- 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素
- 我们可以通过$attrs来访问所有的非props的attribute
App.vue
<template>
<show-info name="zql"
:age="18"
:height="1.88"
ShowHobbies="['唱歌']"
class="active"
address="郑州市" />
<show-info />
</template>
<script>
import ShowInfo from './ShowInfo.vue';
export default {
components: {
ShowInfo
}
}
</script>
<style scoped>
</style>
ShowInfo.vue
<template>
<div class="infos">
<div>姓名:{{name}}</div>
<div>年龄:{{age}}</div>
<div>身高:{{height}}</div>
<div>爱好:{{ShowHobbies}}</div>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default: "我是默认的name"
},
age: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
},
friend: {
type: Object,
default: () => ({ name: "james" })
},
ShowHobbies: {
type: Array,
default: () => ["篮球", "rap", "唱跳"]
}
}
}
</script>
<style scoped>
</style>
// 比如上面的address class="active"属性是一个非prop的attribute,那么这个属性就会被默认添加到子组件的根元素上,F12打开之后在元素上的显示情况
<div class="infos active" address="郑州市">
- 那么我们就可以设置 inheritAttrs:false
export default{
// 设置禁用将,父组件传来的值挂载到子组件的根节点上面,比如这里就是 info 就是根节点
inheritAttrs:false,
props:{
type:Number,
default:10
}
}
- 但是如果我们需要的时候,如何取到这些值?
<template>
<div class="infos">
<div :class="$attrs.class">姓名:{{name}}</div>
<div :class="$attrs.address">年龄:{{age}}</div>
<div>身高:{{height}}</div>
<div>爱好:{{ShowHobbies}}</div>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
default: "我是默认的name"
},
age: {
type: Number,
default: 0
},
height: {
type: Number,
default: 0
},
friend: {
type: Object,
default: () => ({ name: "james" })
},
ShowHobbies: {
type: Array,
default: () => ["篮球", "rap", "唱跳"]
}
}
}
</script>
<style scoped>
</style>
2.1 F12上的元素显示
<div class="infos active" address="郑州市">
<div class="active">姓名:zql</div>
<div class="郑州市">年龄:18</div>
<div>身高:1.88</div>
<div>爱好:['唱歌']</div>
</div>
- 但是如果ShowInfo里面有两个根节点怎么办?设置v-bind="$attrs" 选择绑定到哪个身上
<template>
<div class="infos">
<div :class="$attrs.class">姓名:{{name}}</div>
<div :class="$attrs.address">年龄:{{age}}</div>
<div>身高:{{height}}</div>
<div>爱好:{{ShowHobbies}}</div>
</div>
<div class="others"
v-bind="$attrs">
</div>
</template>
3.1 F12在元素上面的显示结果
<div class="infos">
<div class="active">姓名:zql</div>
<div class="郑州市">年龄:18</div>
<div>身高:1.88</div>
<div>爱好:['唱歌']</div>
</div>
// 设置了v-bind="$attrs"之后,那么没有被接收的参数(比如 设置class类)就会绑定到设定v-bind="$attrs"的根节点身上
<div class="others active" address="郑州市"></div>
- 什么情况下子组件需要传递内容到父组件呢?
- 子传父主要就是触发了事件,然后做出的响应
- 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
- 子组件有一些内容想要传递给父组件的时候
- 我们如何完成上面的操作呢?
- 首先,我们需要在子组件中定义好在某些情况下触发的事件内容名称
- 其实,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
- 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
App.vue
<template>
<div class="app">
<h2>当前计数:{{counter}}</h2>
<!-- 注意点:这里的自定义事件是不需要传参的 -->
<add-counter @add="addBtnClick"></add-counter>
<sub-counter @sub="subBtnClick"></sub-counter>
</div>
</template>
<!--
1. 第一步:
当子组件中发生点击事件之后,那么父组件中的counter就要发生改变
子组件需要先监听事件的发生 methods:{btnClick(){}}
-->
<!--
3. 第三步:
普通浏览器发出的事件是@click="addEvent"
那么组件内部发生的事件是什么呢?
v-on:add="addBtnClick" -> 语法糖:@add="addBtnClick"
-->
<script>
import AddCounter from './AddCounter.vue'
import SubCounter from './SubCounter.vue'
export default {
components: {
AddCounter,
SubCounter
},
data () {
return {
counter: 0
}
},
methods: {
addBtnClick (count) {
this.counter += count
},
subBtnClick (count) {
this.counter -= count
}
}
}
</script>
<!-- scope样式只在当前组件上面有效 -->
<style scoped>
</style>
加操作按钮组件AddCounter.vue
<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</div>
</template>
<!--
2. 第二步:
既然子组件已经可以监听到数据发生改变了,
那么该怎么传递给父组件,修改父组件里面的counter呢?
***自定义事件***
子组件可以发出一个自定义事件,第一个参数是我们自定义的名称,第二个是传递的参数
this.$emit("add",count)
-->
<script>
export default {
methods: {
btnClick (count) {
console.log("btnClick", count)
// 子组件可以发出一个自定义事件,父组件接收
this.$emit("add", count)
}
}
}
</script>
<!-- scope样式只在当前组件上面有效 -->
<style scoped>
</style>
减操作组件SubCounter.vue
<template>
<div class="sub">
<button @click="subBtnClick(1)">-1</button>
<button @click="subBtnClick(5)">-5</button>
<button @click="subBtnClick(10)">-10</button>
</div>
</template>
<script>
export default {
methods: {
subBtnClick (count) {
this.$emit("sub", count)
}
}
}
</script>
<!-- scope样式只在当前组件上面有效 -->
<style scoped>
</style>
1. 按钮发生点击
2. 子组件通过点击事件,发出自定义事件 this.$emit("自定义事件名",参数)
3. 父组件通过给引进来的子组件标签上面添加
<AddCounter @自定义事件名="父组件事件"></AddCounter>触发父组件事件
比如 <add-counter @add="addBtnClick"><add-counter>
4. 触发父组件事件addBtnClick对父组件里面的数据counter进行修改
Vu3新增emits:[" "]
- 第一种方式:写成数组语法 最多人用
<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</div>
</template>
<script>
export default {
// 这样来扫描的时候,只需要来最外层来扫描一下即可,并且这样写之后App.vue引用会有提示
emits: ["add"],
methods: {
btnClick (count) {
console.log("btnClick", count)
// 子组件可以发出一个自定义事件,父组件接收
this.$emit("add", count)
}
}
}
</script>
// scope样式只在当前组件上面有效
<style scoped>
</style>
- 写成对象语法 了解
<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</div>
</template>
<script>
export default {
// 写成对象形式可以进行一些验证的功能
emits: {
add: function (count) {
if (count <= 10) {
return true
}
return false
}
},
methods: {
btnClick (count) {
console.log("btnClick", count)
// 子组件可以发出一个自定义事件,父组件接收
this.$emit("add", count)
}
}
}
</script>
// scope样式只在当前组件上面有效
<style scoped>
</style>