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存在,
所以可以从setu
p传入的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
通过异步机制调用