组件通信
父组件向子组件传递数据
- 父发送:在使用子组件的标签上,以绑定属性的方式向子组件当中传递数据。
- 子接收:暴露一个props属性,值为数组或对象,数组中为接收来的属性名。
语法
vParent.vue
<!-- 在使用子组件的地方,绑定属性的方式向子组件当中传递数据-->
<v-child a="123" :username="username" :arr="arr"></v-child>
<script>
export default {
data(){
return {
username:'张三',
arr:['王五','李四']
}
}
}
</script>
vChild.vue接收
export default {
//使用props,此时prop为数组。
props:['a','username','arr']
}
props验证
props为对象时,可以做props验证。
该对象中以父级传来的属性名作为属性,属性的值为对象{}。
属性的值中的属性:
- type(类型) :常见类型:String,Number,Array,Object,Function,Boolean
- required(必填项),值为布尔值,表示是否必传。
- validator(验证器),函数,第一个参数为接收的值,return后是个判断,如果为true则返回接收的值。
- default(默认值),函数需return。
<template>
<div class="box">
<h4>子组件</h4>
<div>电话:{{tel}}</div>
<div>用户名:{{username}}</div>
<div>年龄:{{age}}</div>
</div>
</template>
<script>
export default {
//1. props如果是个数组是不能加验证的
// props:[ 'tel','username','age' ]
//2. props对象的写法可以为传递过来的数据加验证
props:{
tel:{
required:true ,//代表必须传递
validator(e){ //第一个参数为接收的值。
return e.length == 11 //return后是个判断,如果为true则返回。
}
},
username:{
required:true ,//代表必须传递
},
age:{
type:Number,
validator(value){ //年龄必须是 1,130之间的
return value >= 1 && value <= 130
},
//默认值,如果在使用此组件的时候未传age,则触发默认值
default(){
return 18
}
}
}
}
</script>
子组件向父组件传递数据
子传父必须触发事件。
-
父发送:在使用子组件的标签上,以绑定自定义事件的方式向子组件传递自定义事件。
-
子发送:利用props接收自定义事件,并通过this.$emit()触发自定义事件。
-
父接收:执行自定义事件,改变自身的某些数据。
-
父组件执行自定义事件时,传递的参数需要通过 e v e n t 来 传 递 , 但 是 event来传递,但是 event来传递,但是event可以隐士传递即可省略。
语法
Vue.$emit() 触发事件
Vue.$emit(事件[,args...])//如果传递多个参数往往用对象代替
接收
<v-child @cn="changeName($event)"></v-child>
<!--或:-->
<v-child @cn="changeName"></v-child>
父
<template>
<div class="box">
<h4>子传父语法</h4>
<!-- 子组件想要改变父组件的值,才会用子传父-->
{{username}}
{{isShow}}
<!-- 通过绑定自定义事件把函数传给子组件 -->
<v-child @aa="isShow=true" @cn="changeName($event)"></v-child>
</div>
</template>
<script>
import vChild from './vChild.vue'
export default {
components:{
vChild
},
data(){
return {
username:'父-张三',
isShow:false
}
},
methods:{
changeName($event){
this.username = $event
}
}
}
</script>
子
<template>
<div class="box">
<h4>子组件</h4>
<!-- 点击按钮,触发aa事件 -->
<button @click="changeShowValue">改变父的isShow为true</button>
<button @click="sonCn('李四')">修改name为李四</button>
</div>
</template>
<script>
export default {
props:['aa','cn'],
methods:{
changeShowValue(){
this.$emit('aa') //触发aa事件
},
sonCn(name){
this.$emit('cn',name)
}
}
}
</script>
子传父应用
父
<template>
<div class="box">
子传父应用-弹框
<hr>
<div class="login">
<button @click="isShow1=true">登录</button>
<!-- 这里用v-if渲染,因为在子组件里有挂载完成的方法 v-if如条件不成立则不挂载 -->
<v-toast title="登录成功" v-if="isShow1" @hide="isShow1=false"></v-toast>
</div>
<div class="register">
<button @click="isShow2=true">注册</button>
<v-toast title="注册成功" v-if="isShow2" @hide="isShow2=false"></v-toast>
</div>
<div class="cart">
<button @click="isShow3=true">加入购物车</button>
<v-toast title="加入商品成功" v-if="isShow3" @hide="isShow3=false"></v-toast>
</div>
</div>
</template>
<script>
import vToast from './vToast.vue'
export default {
data(){
return {
isShow1:false, //控制登录提示框
isShow2:false,
isShow3:false
}
},
components:{
vToast
}
}
</script>
子
<template>
<div class="mask">
<span>{{title}}</span>
</div>
</template>
<script>
export default {
props:['hide','title'],
mounted() {
//这里用箭头函数:要mounted的this。
setTimeout(()=>{
this.$emit('hide')
},3000)
},
};
</script>
父传子总结
父传子,传递的数据是父变,子变;子变,父不变,还报错。
若要解决子变,父不变的问题:
-
直接修改:传递对象。因为本质上对象是引用类型,地址不变,谁拿到地址都能修改内存中的数据。
-
间接修改:子传父。
父
<template>
<div class="box">
基本数据类型:{{username}}
<input type="text" v-model="username">
<!-- 2. 传递对象。因为本质上对象是引用类型:它和的是浅拷贝 -->
引用数据类型:{{persons.username}}
<input type="text" v-model="persons.username">
<!-- 使用子组件 -->
<v-child :username="username" :persons="persons"></v-child>
</div>
</template>
<script>
import vChild from './vChild.vue'
export default {
components:{
vChild
},
data(){
return {
username:'zs',
persons:{ username:'ls' }
}
}
}
</script>
子
<template>
<div class="box">
基本类型:{{username}}
<input type="text" v-model="username">
引用类型:{{persons.username}}
<input type="text" v-model="persons.username">
</div>
</template>
<script>
export default {
props:['username','persons']
}
</script>
非父子组件通信 (eventBus)
实现非父子组件通信有三种:
- eventBus 前提: A B组件需要同时存在。
- 本地存储 localStorage sessionStorage
- vuex(状态管理模式): 重点
- cookie/session
eventBus:事件总线,子向父传递用的 $emit 底层就是基于vue中的事件总线的。
- Vue.$on() 监听事件
Vue.$on(事件,(e)=>{//参数为接收的数据 })
- main.js
//Vue 基本上只在main.js中使用。而所有的.vue页面组件都继承于Vue函数
Vue.prototype.$eventBus = new Vue() //在vue原型上绑定一个Vue实例对象
//虽然组件中都有 this.$on和this.$emit,但是每一个组件都是不同的对象,因此this.$on和this.$emit并不共享。
2.要接收数据的组件,监听事件:订阅事件
//监听事件需要时时刻刻监听,因此当组件挂载完成时写监听事件
mounted(){
this.$eventBus.$on('eventB',(e)=>{
this.data = e
})
}
3.发消息的页面:触发事件可以传递数据
//把a中的数据给了b,触发事件可以传递数据,因此a触发事件。
methods:{
sendDataToB(){
this.$eventBus.$emit('eventB','传递的数据')
}
}
ref属性
- 往html内置标签或组件身上绑定ref属性.
- 在js中可以通过 this.$refs.属性名的属性值,访问到对应属性值的DOM节点或子组件实例.
ref操作普通DOM元素
<button ref="btn1" @click="changeBg">按钮</button>
changeBg(){
this.$refs.btn1.style.background = 'red'
},
ref属性操作子组件
- 前提是子组件成功创建。避免与v-if一起使用。
<template>
<div class="box">
<!-- 在子组件上绑定ref属性,父能直接操作子中的数据或函数 -->
<!-- 点击按钮,获取子组件中的数据 -->
<button @click="getChildDataORFn">点击按钮,获取子组件中的数据</button>
<v-ref ref="child"></v-ref>
</div>
</template>
<script>
export default {
methods:{
getChildDataORFn(){
console.log(this.$ref.child1)//Vuecomponent实例。this.$ref.child就等于子组件里的this.
}
}
}
</script>
is属性(动态组件)
- 在html内置标签上绑定is属性解决嵌套问题,即符合w3c规范。
- 结合内置的component组件,进行组件之间切换。
- 通过绑定标签的is属性的方式。例: 则为vlogin组件。
<template>
<div class="box">
<!-- 1. 解决嵌套问题 -->
<ul>
<!-- <v-child></v-child> -->
<li is="v-child"></li>
</ul>
<table>
<tr is='v-child'></tr>
</table>
<!-- 2. 结合内置的component组件,进行组件之间切换 -->
<button @click="cname='v-login'">登录</button>
<button @click="cname='v-register'">注册</button>
<component :is="cname"></component>
</div>
</template>
<script>
import vChild from './vChild.vue'
import vLogin from './vLogin.vue'
import vRegister from './vRegister.vue'
export default {
components:{
vChild,
vLogin,
vRegister
},
data(){
return {
cname:''
}
}
}
</script>
插槽组件(slot)
插槽组件:
<slot></slot>
匿名插槽
父
<!-- 匿名插槽 -->
<v-one>
<!-- 子组件中的内容会加入到子组件中的插槽中 -->
<h2>有志者</h2>
</v-one>
<!-- 京东超市页面 -->
<v-one>
<a href="">回到首页</a>
</v-one>
子
<template>
<div class="box">
匿名插槽
<slot></slot>
</div>
</template>
具名插槽
父
<!-- 具名插槽:其实就是有名字的插槽 -->
<v-two>
<div slot="shang">abcd</div>
<div slot="xia">aaaa</div>
</v-two>
子
<template>
<div class="box">
<slot name="shang"></slot>
<div>---------------</div>
<slot name="xia"></slot>
</div>
</template>
插槽作用域(重要)
指令
- 接收子组件通过槽口传递的所有数据的方式有两种:
slot-scope:指令(老)
v-slot:指令(新) - slot-scope或者v-slot里的名字可以自定义.
原理
- 子组件中的html标签由父级决定。
- 父组件需要将数据(arr)传给子组件。
- 子组件拿到数据需要加工再传给父组件,通过给slot组件(插槽)绑定属性的方式。
- 父组件里在子组件模板中加一层tamplate模板,并通过v-slot指令获取子组件传来的数据。
父
<template>
<div class="box">
<!-- 作用域插槽语法 -->
<v-three :arr="arr">
<!-- <template slot-scope="props"> -->
<!-- v-slot拿到子组件数据 -->
<template v-slot="props">
{{props.rows}}
<ul>
<li v-for="(item,index) in props.rows" :key="index">{{item}}</li>
</ul>
</template>
</v-three>
</div>
</template>
<script>
export default {
data(){
return {
arr:[]
}
}
}
</script>
子
<template>
<div class="box">
<!-- 把数据通过插槽传给父 -->
<slot :rows="arr2"></slot>
</div>
</template>
<script>
export default {
props:['arr'],
data(){
return {
arr2:[]
}
},
mounted(){
this.arr2 = this.arr.map( item=>{
return item + 1
} )
}
}
</script>
分析:
<el-table :data="tableData" border style="width: 100%">
<el-table-column
prop="zip"
label="邮编"
width="120">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<!-- 这里父组件用到了作用域插槽 ,通过scope.row获取父组件的数据 -->
<template slot-scope="scope">
<button>查看</button>
<button>编辑</button>
</template>
</el-table-column>
</el-table>
mixin混入
- 操作js,以js形式存在。
- mixin是一个对象,可以将这个对象混入到组件中。
- 组件中的属性选项都可以在mixin对象中声明。
- 组件引入需用mixins属性,值为数组。
- 引入的数据会添加,并且不会覆盖。
作用:
将组件中共享的数据存放在mixin中,哪个组件需要使用数据就将其引入。
1.创建mixin.js文件
export default {
data(){
return {
addtime:'2021-11-11'
}
},
mounted(){
console.log('这是mixins.js中的mounted')
}
}
2.在其它组件中引入并使用混入
<script>
// 引入混入的js文件
import mixins1 from './mixins.js'
export default {
//放到组件中的对象中
mixins:[ mixins1 ],//在当前组件可以使用addtime数据。
data(){
return {
username:'zs'
}
}
}
</script>
缓存组件(keep-alive)
- 组件keep-alive,作为缓存组件.以标签形式。
- 使用keep-alive标签包裹的组件就是缓存组件。
- 但是缓存组件中的组件生命周期会一直存在,因此销毁阶段不会执行。因此在缓存组件中写的事件需要用到钩子函数。
缓存组件钩子函数
- activated 组件激活时的状态
- deactivated 组件失活时的状态
父
<template>
<div class="box">
缓存组件
<button @click="falg = true">a</button>
<button @click="falg = false">b</button>
<!-- 保留组件进行缓存 -->
<keep-alive>
<v-a v-if="falg"></v-a>
<v-b v-else></v-b>
</keep-alive>
</div>
</template>
<script>
import vA from "./vA.vue";
import vB from "./vB.vue";
export default {
data() {
return {
falg: true,
};
},
components: {
vA,
vB,
},
};
</script>
a
<template>
<div class="box">
a
</div>
</template>
<script>
export default {
mounted(){
console.log('a的请求');
},
activated(){
// console.log('a激活了');
console.log('a绑定了滚动事件')
},
deactivated(){
// console.log('a失活了')
console.log('要删除a的滚动事件')
}
}
</script>
<style>
</style>
b
<template>
<div class="box">
B
</div>
</template>
<script>
export default {
created(){
console.log('b发送请求')
}
}
</script>
<style>
</style>