常见通信方式总和

image.png

父子相互都能通信

Props父子相互通信

概述:props是使用频率最高的一种通信方式,常用与 :父 ↔ 子

  • 父传子:属性值是非函数
  • 子传父:属性值是函数

注意:props可以让子直接拿到父亲传过来的属性,儿子使用**defineProps([父亲方法与属性的数组])**,

​ 但是父亲不可以直接去拿儿子的属性,要要拿要遵循以下步骤

​ 1.自己定义一个有属性的方法,变成绑定事件传递给儿子组件。

​ 2.儿子组件定义触发事件,传入父亲想要的属性

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="father">
<h3>父组件,</h3>
<h4>我的车:{{ car }}</h4>
<h4>儿子给的玩具:{{ toy }}</h4>
<Child :car="car" :getToy="getToy"/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 数据
const car = ref('奔驰')
const toy = ref()
// 方法
function getToy(value:string){
toy.value = value
}
</script>

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="child">
<h3>子组件</h3>
<h4>我的玩具:{{ toy }}</h4>
<h4>父给我的车:{{ car }}</h4>
<button @click="getToy(toy)">玩具给父亲</button>
</div>
</template>

<script setup lang="ts" name="Child">
import { ref } from "vue";
const toy = ref('奥特曼')

defineProps(['car','getToy'])
</script>

v-model父子间相互通信

  1. 概述:实现 父↔子 之间相互通信。

  2. 前序知识 —— v-model的本质

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 使用v-model指令 -->
    <input type="text" v-model="userName">

    <!-- v-model的本质是下面这行代码 -->
    <input
    type="text"
    :value="userName"
    @input="userName =(<HTMLInputElement>$event.target).value"
    >
  3. 组件标签上的v-model的本质::moldeValueupdate:modelValue事件。

    1
    2
    3
    4
    5
    <!-- 组件标签上使用v-model指令 -->
    <AtguiguInput v-model="userName"/>

    <!-- 组件标签上v-model的本质 -->
    <AtguiguInput :modelValue="userName" @update:model-value="userName = $event"/>

    AtguiguInput组件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    <div class="box">
    <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
    <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
    <input
    type="text"
    :value="modelValue"
    @input="emit('update:model-value',$event.target.value)"
    >
    </div>
    </template>

    <script setup lang="ts" name="AtguiguInput">
    // 接收props
    defineProps(['modelValue'])
    // 声明事件
    const emit = defineEmits(['update:model-value'])
    </script>
  4. 也可以更换value,例如改成abc

    1
    2
    3
    4
    5
    <!-- 也可以更换value,例如改成abc-->
    <AtguiguInput v-model:abc="userName"/>

    <!-- 上面代码的本质如下 -->
    <AtguiguInput :abc="userName" @update:abc="userName = $event"/>

    AtguiguInput组件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div class="box">
    <input
    type="text"
    :value="abc"
    @input="emit('update:abc',$event.target.value)"
    >
    </div>
    </template>

    <script setup lang="ts" name="AtguiguInput">
    // 接收props
    defineProps(['abc'])
    // 声明事件
    const emit = defineEmits(['update:abc'])
    </script>
  5. 如果value可以更换,那么就可以在组件标签上多次使用v-model

    1
    <AtguiguInput v-model:abc="userName" v-model:xyz="password"/>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div class="father">
<h3>父组件</h3>
<h4>{{ username }}</h4>
<h4>{{ password }}</h4>
<!-- v-model用在html标签上,等价绑定方式 -->
<!-- <input type="text" v-model="username"> -->
<!-- <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"> -->

<!-- v-model用在组件标签上 -->
<!-- elementUI组件上是可以加v-model的,但是自己定义的不能直接加 -->
<!-- <el-input></el-input> -->

<!-- <AtguiguInput v-model="username"/> -->
<!-- <AtguiguInput
:modelValue="username"
@update:modelValue="username = $event"
/> -->

<!-- 修改modelValue,简化代码,用v-model进行 -->
<AtguiguInput v-model:ming="username" v-model:mima="password"/>
</div>
</template>

<script setup lang="ts" name="Father">
import { ref } from "vue";
import AtguiguInput from './AtguiguInput.vue'
// 数据
let username = ref('zhansgan')
let password = ref('123456')
</script>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<input
type="text"
:value="ming"
@input="emit('update:ming',(<HTMLInputElement>$event.target).value)"
>
<br>
<input
type="text"
:value="mima"
@input="emit('update:mima',(<HTMLInputElement>$event.target).value)"
>
</template>

<script setup lang="ts" name="AtguiguInput">
defineProps(['ming','mima'])
const emit = defineEmits(['update:ming','update:mima'])
</script>

$refs与$parent父子通信

1.$refs实现父传子

而且可以,定义一次传给所有儿子

2.$parent实现子传父

多个儿子都可以对同一父亲进行相关的影响

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template>
<div class="father">
<h3>父组件</h3>
<h4>房产:{{ house }}</h4>
<button @click="changeToy">修改Child1的玩具</button>
<button @click="changeComputer">修改Child2的电脑</button>
<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref,reactive } from "vue";
let c1 = ref()
let c2 = ref()

// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中的
/* let obj = reactive({
a:1,
b:2,
c:ref(3)
})
let x = ref(4)

console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x) */


// 数据
let house = ref(4)
// 方法
function changeToy(){
c1.value.toy = '小猪佩奇'
}
function changeComputer(){
c2.value.computer = '华为'
}
//给所有儿子加书
function getAllChild(refs:{[key:string]:any}){
console.log(refs)
for (let key in refs){
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({house})
</script>

子组件一号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<h4>书籍:{{ book }} 本</h4>
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
</div>
</template>

<script setup lang="ts" name="Child1">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
let book = ref(3)

// 方法
function minusHouse(parent:any){
parent.house -= 1
}

// 把数据交给外部
defineExpose({toy,book})

</script>

子组件二号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4>书籍:{{ book }} 本</h4>
</div>
</template>

<script setup lang="ts" name="Child2">
import { ref } from "vue";
// 数据
let computer = ref('联想')
let book = ref(6)
// 把数据交给外部
defineExpose({computer,book})
</script>

任意组件通信

任意组件通信mitt

概述:与消息订阅与发布(pubsub)功能类似,可以实现任意组件间通信。

安装mitt

1
npm i mitt

【第一步】新建文件:src\utils\emitter.ts(一般命名为utils或者tools)

1
2
3
4
5
6
7
8
// 引入mitt 
import mitt from "mitt";

// 创建emitter
const emitter = mitt()

// 创建并暴露mitt
export default emitter

【第二步】接收数据的组件中:绑定事件、同时在销毁前解绑事件:

1
2
3
4
5
6
7
8
9
10
11
12
import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";

// 绑定事件
emitter.on('send-toy',(value)=>{
console.log('send-toy事件被触发',value)
})

onUnmounted(()=>{
// 解绑事件
emitter.off('send-toy')
})

【第三步】:提供数据的组件,在合适的时候触发事件

1
2
3
4
5
6
import emitter from "@/utils/emitter";

function sendToy(){
// 触发事件
emitter.emit('send-toy',toy.value)
}

pinia任意组件通信

使用pinia也可以实现任意组件通信的效果,具体时搭建store仓库,在main.ts中创建相关的pinia对象,然后写相关的ts文件然后暴露出去,提供给所需的组件们使用。

祖传孙通信

atter实现祖传孙通信

具体说明:$attrs是一个对象,包含所有父组件传入的标签属性

注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

下列父组件用

父组件:需要绑定属性进行传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="father">
<h3>父组件</h3>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)

function updateA(value){
a.value = value
}
</script>

子组件:使用atter接受所有父组件的数据

1
2
3
4
5
6
7
8
9
10
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>

<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>

孙组件:可以直接看出,孙组件可以在经过了父组件的传递后,能够接受到父组件传来的所有属性和方法了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(666)">点我更新A</button>
</div>
</template>

<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d','x','y','updateA'])
</script>

provide、inject实现祖传孙通信

在祖先组件中通过provide配置向后代组件提供数据,在后代组件中通过inject配置来声明接收数据

【第一步】父组件中,使用provide提供数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="father">
<h3>父组件</h3>
<h4>资产:{{ money }}</h4>
<h4>汽车:{{ car }}</h4>
<button @click="money += 1">资产+1</button>
<button @click="car.price += 1">汽车价格+1</button>
<Child/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref,reactive,provide } from "vue";
// 数据
let money = ref(100)
let car = reactive({
brand:'奔驰',
price:100
})
// 用于更新money的方法
function updateMoney(value:number){
money.value += value
}
// 提供数据
provide('moneyContext',{money,updateMoney})
provide('car',car)
</script>

【第二步】孙组件中使用inject配置项接受数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>资产:{{ money }}</h4>
<h4>汽车:{{ car }}</h4>
<button @click="updateMoney(6)">点我</button>
</div>
</template>

<script setup lang="ts" name="GrandChild">
import { inject } from 'vue';
// 注入数据
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(x:number)=>{}})
let car = inject('car')
</script>

子传父操作

自定义事件祖传父

儿子使用defineEmits传递想要要给父组件的数据,用emit先定义好事件名字

注意:子触发事件之后会传来数据,过来之后父亲还是要写函数和属性来接收数据。

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="father">
<h3>父组件</h3>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<!-- 给子组件Child绑定事件 -->
<Child @send-toy="saveToy"/>
</div>
</template>

<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from "vue";
// 数据
let toy = ref('')
// 用于保存传递过来的玩具
function saveToy(value:string){
console.log('saveToy',value)
toy.value = value
}
</script>

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emit('send-toy',toy)">测试</button>
</div>
</template>

<script setup lang="ts" name="Child">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
// 声明事件
const emit = defineEmits(['send-toy'])
</script>

插槽子传父

默认插槽

数据在父组件中给与内容到子组件的插槽位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
父组件中:
<Category title="今日热门游戏">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<!-- 默认插槽 -->
<slot></slot>
</div>
</template>

具名插槽

父组件可以分别放多个内容不同的插槽进入子组件中(数据在父组件中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
父组件中:
<Category title="今日热门游戏">
<template v-slot:s1>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
<template #s2>
<a href="">更多</a>
</template>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>

作用域插槽

数据在子组件中,父组件在子组件定义了绑定事件后,可以用params来调用子组件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
父组件中:
<Game v-slot="params">
<!-- <Game v-slot:default="params"> -->
<!-- <Game #default="params"> -->
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</Game>

子组件中:
<template>
<div class="category">
<h2>今日游戏榜单</h2>
<slot :games="games" a="哈哈"></slot>
</div>
</template>

<script setup lang="ts" name="Category">
import {reactive} from 'vue'
let games = reactive([
{id:'asgdytsa01',name:'英雄联盟'},
{id:'asgdytsa02',name:'王者荣耀'},
{id:'asgdytsa03',name:'红色警戒'},
{id:'asgdytsa04',name:'斗罗大陆'}
])
</script>