1. 前言
vue也是组件化开发框架,对于这种组件化开发来说,组件之间的通信方式通常都是非常重要的
所以单独开一个篇章来总结下有哪些通信方式
2. 首先列出常用的组件通信方式
1.props
2.$emit/$on
3.$children/$parent
4.$attrs / $listeners
5.ref
6.$root
7.eventBus
8.vuex
列出来后,可以自己先考虑下应用场景
下面不饶弯子了,以组件的关系来解说通信方式
3. 父子组件通信
3.1 props 父传子
1.父组件以属性的方式传值给子组件
2.子组件通过props方式接收数据
3.1.1父组件核心代码
在父组件中引入子组件并绑定parentData自定义属性
<Child:parentData="parentData"></Child >
<script>
import Child from '@/components/child'
export default{
name:'Parent',
components:{Child},
data(){
return{
parentData:'我是父组件向子组件传递的值-props方式'
}
}
}
</script>
3.1.2 子组件核心代码
1.在子组件中使用 props 接收父组件传递的数据,
2.props 里的名字跟父组件定义的属性名一致
<template>
<div>我是父组件的数据:{{parentData}}</div>
<div>我是父组件传递修改后的数据:{{mydata}}</div>
</template>
<script>
export default{
name:'Child',
props:{
parentData:{
type:String,
default:''
}
}
data(){
mydata:'俺的小破站 '+ this.parentData
},
watch:{
parentData(newVal){
this.mydata='俺的小破站 '+ newVal
}
},
}
</script>
Vue的
单向数据流机制,子组件不能够直接去修改父组件传递的值修改的,否则能改的话那父组件的值就被污染了。
但是子组件内想要修改父组件传过来的值却不“污染”父组件的话,
可以在子组件内定义一个变量mydata去接收parentData数据,并使用 watch 监听parentData数据的变更
3.2 $emit/$on 子传父
1.子组件绑定自定义事件
2.使用 $emit() 触发更改数据
3.2.1 子组件核心代码
<el-button @click="handleEmit">告诉父组件我要更改数据啦</el-button>
<script>
export default{
name:'Child',
methods:{
handleEmit(){
this.$emit('triggerEmit','我是来自子组件的数据')
}
}
}
</script>
3.2.2父组件核心代码
1.父组件定义并绑定子组件传递的triggerEmit事件
2.triggerEmit事件名需跟子组件 $emit() 的事件名
<Child @triggerEmit="changeData"></Child>
<script>
import Child from '@/components/child'
export default{
name:'Parent',
components:{Child},
methods:{
changeData(name){
console.log(name) // => 我是来自子组件的数据
}
}
}
</script>
3.3 $parent/$children
1.子组件通过 $parent 获得父组件实例
2.父组件通过 $children 获得子组件实例数组
3.3.1 子组件
<template>
<div>我是子组件</div>
</template>
<script>
export default{
name:"Child",
data(){
return{
childTitle: '我是子组件的数据'
}
},
methods:{
childHandle(){
console.log('我是子组件的方法')
}
},
created(){
console.log(this.$parent)
console.log(this.$parent.parentTitle) // => 我是父组件的数据
this.$parent.parentHandle() // => 我是父组件的方法
}
}
</script>
this.$parent可以获取到父组件的方法、data的数据等,并可以直接使用和执行。
3.3.2 父组件
<template>
<div>
<Child>我是父组件</Child>
</div>
</template>
<script>
import Child from './child.vue'
export default{
name: 'parent',
components:{
Child
},
data(){
return{
parentTitle: '我是父组件的数据'
}
},
methods:{
parentHandle(){
console.log('我是父组件的方法')
}
},
mounted(){
console.log(this.$children)
console.log(this.$children[0].childTitle) // => 我是子组件的数据
this.$children[0].childHandle() // => 我是子组件的方法
}
}
</script>
3.3.3 注意钩子的使用
父组件是在
mounted()生命周期中获取子组件实例的,并且获取的实例是一个数组形式
3.3.4 问题 层级发生变化的时候咋办呢 ???
1.源码其实有更高层次的封装,在引用
parent到时候抽象一个更高级的方法类似dispatch,
2.直接指定比较重要的父组件的类型或者名称,在循环查找的时候避免程序的脆弱性
3.4 ref
父组件使用 $refs 获得组件实例
<template>
<div>
<Child ref="child"></Child >
</div>
</template>
<script>
import Child from './child.vue'
export default{
name: 'parent',
components:{
Child
},
mounted(){
console.log(this.$refs.child ) /*组件实例*/
}
}
</script>
1.注意 钩子
mounted
- 父组件就可以直接使用this.$refs.xx获取子组件的实例
3.5 $attrs/$listeners
4. 兄弟组件
核心就是找共同点, 搭建桥梁,中间人,话事人的感觉
4.1 $parent
既然是兄弟往上找 总能找到共同的祖先
不常用,可以参考文章上面 的 写法
4.2 $root
其实 根也是共享的
4.3 eventBus 也是都可以访问的
4.3.1 创建一个Vue实例
作为调度中心 eventBus
import Vue from "vue"
export default new Vue()
4.3.2 需要进行通信的组件中 引入
<template>
<div>
<div>我是通信组件A</div>
<el-button @click="changeName">修改</el-button>
</div>
</template
<script>
import { EventBus } from "../bus.js"
export default{
data(){
return{}
},
methods:{
changeName(){
EventBus.$emit("editName", '俺的小破站')
}
}
}
</script>
4.3.3 监听事件
<template>
<div>我是通信组件B</div>
</template
<script>
import { EventBus } from "../bus.js"
export default{
data(){
return{}
},
mounted:{
EventBus.$on('editName',(name)=>{
console.log(name) // 俺的小破站!
})
}
}
</script>
4.4 vuex
具体可以看我之前写的vuex-0系列
5. 跨级组件
5.1eventBus
5.2 vuex
1.相当于一个公共数据的仓库
2.提供一些方法管理仓库数据
3.具体可以看我之前写的vuex-0系列
5.3.provide/inject
5.3.1 简介
父组件使用 provide 注入数据
子组件使用 inject 使用数据
5.3.2 父组件
export default{
provide: {
return{
provideName: '俺的小破站'
}
}
}
5.3.3 分析
provideName这个变量可以提供给它其下的所有子组件,包括曾孙、孙子组件等,只需要使用 inject 就能获取数据
5.3.4 子组件
export default{
inject: ['provideName'],
created () {
console.log(this.provideName) // 俺的小破站
}
}
5.3.5 优缺点
1.父组件不需要知道哪个组件使用它提供出去的数据
2.子组件不需要知道这个数据从哪里来
3.更加的原生,不会对第三方库产生依赖
缺点:单向的;只能祖辈向子辈传值
6.vue3 通信方式
6.1 props和emit
setup函数可以接受两个参数, prop 和 context ,其中context可以解构出emit slots attrs
利用 emit实例来传参
6.2 子组件 代码
<template>
<el-button @click="handle">子组件 ---点击</el-button>
<div>我是父组件传过来的数据:{{name}}</div>
</template>
<script>
export default {
name:"Child",
props: {
name: {
type: String,
default: ''
}
},
setup(props,{ emit }) {
console.log(props.name) // yzs
function handle() {
emit('handleClick', 'Vue3 学起来')
}
return {
handle
}
}
}
</script>
6.3 简要分析
Vue3中没有this的概念了,所以就不会有this.$emit存在,
所以可以从setup传入的context解构出emit实例,从而派发事件给父组件
6.4 父组件
<template>
<Test name="yzs" @handleClick="myClick">父组件---点呀</Test>
</template>
<script>
import Test from './index.vue'
export default {
name:"parent",
components: { Test },
setup() {
function myClick(name) {
console.log(name)// Vue3 学起来
}
return {
myClick
}
}
}
</script>
7. ref
Vue3我们可以从Vue中导出 ref 方法,得到子组件的实例
7.1 分析
1.通过,在子组件声明ref属性,
2.属性值必须和const btnRef = ref(null)这里声明的变量名一致,否则会报错
3.拿到子组件实例后就可以直接调用组件的sendParent方法了
7.2 父组件代码
<template>
<Test ref="btnRef">
<el-button @click="click">点我呀</el-button>
</Test>
</template>
<script>
import { ref } from "vue"
import Test from './index.vue'
export default {
components: {Test},
setup() {
const btnRef = ref(null)
function click() {
btnRef.value?.sendParent() // 我是给父组件调用的方法
}
return {
btnRef,
click
}
}
}
</script>
这里使用的btnRef.value?.是可选链操作符语法,
代表?前面的值为true才继续执行后面的语句
7.3 使用
<template>
<slot></slot>
</template>
<script>
export default {
setup() {
function sendParent() {
console.log("我是给父组件调用的方法")
}
return {
sendParent
}
}
}
</script>
8. provide/inject
和vue2差不离儿
8.1 父组件
<template>
<Test></Test>
</template>
<script>
import { provide } from "vue"
import Test from './index.vue'
export default {
components: {Test},
setup() {
//我已经把数据注入到fromParent里面去了
provide('fromParent', '俺的小破站')
return {}
}
}
</script>
8.2 子组件
<template>
<slot></slot>
<div>我是父组件注入的数据:{{parentData }}</div>
</template>
<script>
import { inject } from "vue"
export default {
setup() {
//我来去父组件注入的数据
let parentData = inject('fromParent')
return {
parentData
}
}
}
</script>
子孙组件使用
inject获取到父组件注入的数
8.3 区别
其实主要就是 Vue3采用了 这个模块化
所以inject 和 provide 需要单独导入
9. nextTick 是什么
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
官方案例
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
// 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示)
Vue.nextTick()
.then(function () {
// DOM 更新了
})
10.为什么 需要nextTick
由于vue的
异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法
vue在
更新DOM时是异步执行的.只要侦听到数据变化,vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更.如果同一个watcher被多次触发,只会会被推入到队列中1次.
这种在缓冲时取出重复数据对于避免不必要的计算和DOM操作是非常重要的.
nextTick方法会在队列中加入一个回调函数,确保该函数在前面的DOM 操作完成后才调用
所以当我们想在数据
修改后立即看到都DOM执行结果就需要用到nextTick方法
11. 源码分析
主要就是通过
callbacks回调函数的数组,存的就是nextTick传的函数,
使用函数timerFunc通过异步机制调用