Vue3(五) 组件通信大汇总

文章目录

一、props

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

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

父传子

html 复制代码
<!--父组件-->
<Child :car="car" />
<script>
	let car = ref('奔驰')
</script>
 
<!--子组件接收数据-->
 <h4>父亲给的车:{{ car }}</h4>
 <script>
    defineProps(['car'])
</script>

子传父:

需要父提前给子传递函数,通过参数接收子传递的数据

html 复制代码
<!--父组件-->
	<h4 v-if="toy">子组件给的玩具:{{ toy }}</h4>
	<Child :car="car" :sendToy="sendToy" />
<script>
	let toy = ref('')
	function sendToy(value: string) {
		toy.value = value
	}
</script>
 
<!--子组件传递数据-->
<button @click="sendToy(toy)">把玩具给子组件</button>
<script setup lang="ts" name="Child">
	import { ref } from 'vue'
	let toy = ref('奥特曼')
	defineProps(['car', 'sendToy'])
</script>

二、自定义事件

触发事件的语法

typescript 复制代码
 let emit = defineEmits(['函数名'])
 // 触发事件
 emit('函数名',需要传递的参数)

举例:

html 复制代码
<!--父组件自定义事件send-toy-->
	<h4 v-show="toy">子组件传递过来的玩具:{{ toy }}</h4>
	<Child @send-toy="getToy" />
<script>
	let toy = ref('')
	function getToy(value: string) {
		toy.value = value
	}
</script>
 
<!--子组件接收数据-->
<!--触发send-toy事件,父组件进而调用了getToy函数,并收到了参数toy-->
 <button @click="emit('send-toy', toy)">玩具给父组件</button>
 <script>
   let toy = ref('奥特曼')
   // 接收之后,emit('send-toy')就是在触发事件
   let emit = defineEmits(['send-toy'])
</script>

emit接收两个事件:

javascript 复制代码
let emit = defineEmits(['a','b'])

触发时:

javascript 复制代码
emit('a')
emit('b')

三、mitt

消息订阅pubsub,全局事件总线$bus,mitt都是一样的思想。

接收数据的:提前绑好事件(提前订阅消息)

提供数据的:在合适的时候触发事件(发布消息)

  1. 安装mitt:npm i mitt

  2. 新建文件:src\utils\emitter.ts

    typescript 复制代码
    // 引入mitt 
    import mitt from "mitt";
    
    // 创建emitter
    const emitter = mitt()
    
    // 创建并暴露mitt
    export default emitter

    绑定事件:emitter.on(事件名,()=>{回调函数逻辑 })

    触发事件:emitter.emit(事件名,传递的参数)

    解绑事件:emitter.off(事件名)

    解绑所有事件:emitter.all.clear()

  3. 接收数据的组件绑定事件+在组件卸载前解绑事件

  4. 发送数据的组件触发事件

四、v-model

UI 组件库大量使用v-model进行通信。

1.v-model的本质

html 复制代码
 <!-- v-model双向绑定,用在html标签上 -->
 <input type="text" v-model="username">
 <!-- 上边代码的本质是:  -->
 <!--  
	 :value="username":实现数据到页面
	  @input...:实现页面到数据
  -->
 <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value">

(<HTMLInputElement>$event.target).value其实就是$event.target.value,因为ts语法的限制,需要加断言。

2.v-model用在组件标签上

html 复制代码
<!-- 修改modelValue -->
<ChildInput v-model="username" />
<!-- 本质是:默认写modelValue,也可以修改,修改见第3点更换modelValue -->
<ChildInput :modelValue="username" @update:modelValue="username = $event" />

:modelValue="username":props实现父传子
@update...:类似于自定义事件,实现子传父;$event是子组件传过来的值

ChildInput 组件中:

html 复制代码
<template>
    <!--将接收的modelValue值赋给input元素的value属性,目的是:为了呈现数据 -->
	<!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件,触发时将$event.target.value通过参数传回去-->
  <input type="text" 
  :value="modelValue"
   @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)">
</template>

<script setup lang="ts" name="ChildInput">
// 接收父组件传递的值
defineProps(['modelValue'])
// 声明事件,
let emit = defineEmits(['update:modelValue'])
</script>

流程就是:

(1) 子组件将用户输入的值传给父组件:

子组件中通过触发事件将$event.target.value的值(也就是用户输入的值)传给父组件,父组件将username的值改为用户输入的值。

(2) 用户输入的值传给子组件进而呈现在页面上:

通过父组件的modelValue将新的username值传给子组件,子组件通过props接收后赋给input标签的value属性,呈现用户输入的数据。

3.更换modelValue

需要在v-model后加上别名

html 复制代码
<!-- 修改modelValue -->
 <ChildInput  v-model:mima="password" />
 <!-- 本质 -->
<ChildInput :mima="username" @update:mima="username = $event" />

4.更换modelValue时,可以在组件标签上多次使用v-model


注意:

父组件中:@update:modelValue="username = $event"

子组件中: @input="emit('update:modelValue', $event.target.value)

什么时候用$event,什么时候用$event.target.value

$event是什么?什么时候能.target

  1. 对于原生事件,$event就是事件对象,====>能.target
  2. 对于自定义事件,$event就是触发事件时,所传递的数据,===>不能.target

五、$attrs

  • 用来实现祖孙组件之间的通信。
  • 当父组件向子组件通过props传值时,子组件``defineProps()只接收部分值,剩下没接收的值则存在了$attrs里。

1. 未读取的数据存在$attrs

html 复制代码
<!-- 父组件 -->
<div class="father">
	<h3>父组件</h3>
	<h4>a:{{ a }}</h4>
	<h4>b:{{ b }}</h4>
	<h4>c:{{ c }}</h4>
	<h4>d:{{ d }}</h4>
	<!-- v-bind="{ x: 100, y: 200 }" 等价于 :x="100" :y="200" -->
	<Child :a="a" :b="b" :c="c" :d="d" v-bind="{ x: 100, y: 200 }" />
	<!-- 等价于 -->
	<!-- <Child :a="a" :b="b" :c="c" :d="d" :x="100" :y="200" /> -->
</div>
<!-- 子组件 -->
	<h4>a:{{ a }}</h4>
	<h4>其他:{{ $attrs }}</h4>
	<script>
		defineProps(['a'])
    </script>

2. 祖向孙传数据

html 复制代码
<!--Father.vue-->
	<Child :a="a" :b="b" :c="c" :d="d" v-bind="{ x: 100, y: 200 }" />

<!--Child.vue,中间组件啥也不接受,传给GrandChild-->
	<GrandChild v-bind="$attrs" />

<!--GrandChild.vue-->
	<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>
	</div>
<script setup lang="ts" name="GrandChild">
	defineProps(['a', 'b', 'c', 'd', 'x', 'y'])
</script>

3. 孙向祖传数据
Father.vue:通过props传递函数

html 复制代码
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{ x: 100, y: 200 }" :updateA="updateA" />
		<!-- 等价于 -->
<script setup lang="ts" name="Father">
	function updateA(value: any) {
		a.value = value
	}
</script>

Child.vue

html 复制代码
<!--Child.vue,中间组件啥也不接受,传给GrandChild-->
<GrandChild v-bind="$attrs" />

GrandChild.vue:调用函数

html 复制代码
<button @click="updateA(666)">更新A</button>
<script setup lang="ts" name="GrandChild">
	defineProps(['a', 'b', 'c', 'd', 'x', 'y', 'updateA'])
</script>

六、$refs,与¥parent

正确的是$ref$parent,标题打这两个术语会出错。
$refs:用于父传子
$parent:用于子传父

属性 说明
$refs 值为对象,包含所有被ref属性标识的DOM元素或组件实例。
$parent 值为对象,当前组件的父组件实例对象。

1. 回顾标签ref属性修改组件信息

子组件1Child1.vue

html 复制代码
<template>
	<div class="child1">
		<h3>子组件1</h3>
		<h4> 玩具:{{ toy }}</h4>
		<h4> 书籍:{{ book }}本</h4>
	</div>
</template>

<script setup lang="ts" name="Child1">
import { ref } from 'vue';
let toy = ref('奥特曼')
let book = ref(3)
// 向外暴露出来,父组件才能读取到子组件的值
defineExpose({ toy, book })
</script>

子组件2Child2.vue结构类似:

html 复制代码
...
<script setup lang="ts" name="Child2">
...
let computer = ref('联想')
let book = ref(6)
// 向外暴露
defineExpose({ computer, book })
</script>

父组件Father.vue修改子组件信息

html 复制代码
<template>
	<div class="father">
		<h3>父组件</h3>
		<h4>房产有{{ house }}套</h4>
		<button @click="changeToy">修改子组件1的玩具</button>
		<button @click="changeComputer">修改子组件2的电脑</button>
		<Child1 ref="c1" />
		<Child2 ref="c2" />
	</div>
</template>

<script setup lang="ts" name="Father">
	 ...
	let house = ref(4)
	// 获取到两个组件的实例对象
	let c1 = ref()
	let c2 = ref()
	
	function changeToy() {
		c1.value.toy = '小猪佩奇'
	}
	function changeComputer() {
		c2.value.computer = '华为'
	}
</script>

父组件通过ref获取子组件实例对象后,可以对其属性进行修改的前提条件时,子组件将该属性defineExpose()暴露出来。

2. $refs实现父修改所有子组件信息

父组件Father.vue

html 复制代码
<button @click="getAllChild($refs)">增加所有子组件的书数量</button>
<script>
	// 参数或者写成 refs:{[key:string]:any}
	function getAllChild(refs: any) {
		console.log('refs',refs);
		for (let key in refs) {
		// 修改所有子组件的书本属性
			refs[key].book += 3
		}
	}
</script>

3. $parent实现子修改父

父组件Father.vue将数据暴露出去,子组件才能获取到该属性并修改:

typescript 复制代码
let house = ref(4)
defineExpose({ house })

子组件:Child1.vue

html 复制代码
<button @click="minusHouse($parent)">卖出一套房产</button>
<script>
	function minusHouse(parent: any) {
		console.log('parent',parent);
		parent.house -= 1
	}
</script>

这里读取数据不用写.value见之前的记录。

七、provide、inject祖孙通信

$attrs实现祖孙通信了,打扰了中间层;而provideinject不会打扰中间层。

提供数据:provide('数据或方法',数据或方法)

接收(注入)数据:inject('数据')

1. 祖传孙

祖组件:Father.vue

html 复制代码
<script setup lang="ts" name="Father">
	let car = ref('奔驰')
	// 传递的数据,通过provide提供
	provide('car', car)
</script>

孙组件:GrandChild.vue

html 复制代码
  <div class="grand-child">
    <h3>我是孙组件</h3>
    <h4>车子:{{ car }}</h4>
  </div>
<script setup lang="ts" name="GrandChild">
   // 1.引入inject
	import { inject } from 'vue'
   // 2. 读取数据;
	let car = inject('car')
</script>

注意,如果接收数据时

typescript 复制代码
   let car = inject('car2')

祖组件没有提供名为car2的数据,则孙组件读取到的car值是undefined;

孙组件可设置默认值

typescript 复制代码
  let car = inject('car2','奥迪') // car的默认值是奥迪

2. 孙传祖

思想还是祖给孙传递函数,孙通过参数的方式来给祖传信息。

祖组件:Father.vue

html 复制代码
<script setup lang="ts" name="Father">
import { ref, provide } from 'vue';
let money = ref(500)

function spendMoney(value: number) {
  money.value -= value
}
// 可以将数据或方法一起传递
provide('moneyContext', { money, spendMoney })
</script>

孙组件:GrandChild.vue

html 复制代码
 <div class="grand-child">
   <h3>我是孙组件</h3>
   <h4>银子:{{ money }}万元</h4>
   <h4>车子:{{ car }}</h4>
   <button @click="spendMoney(6)">花钱</button>
 </div>

<script setup lang="ts" name="GrandChild">
	import { inject } from 'vue'
	// 解构取值
	// let { money, spendMoney } = inject('moneyContext')
	// 上面的方式会有警告,ts不确定是否会有money和spendMoney,所以给个默认值
	let { money, spendMoney } = inject('moneyContext', { money: 0, spendMoney: (param: number) => { } })
</script>

注意:为什么传递的不是money.value,而是money?

typescript 复制代码
provide('moneyContext', { money.value, spendMoney })

如果传递.value,则传递的只是一个值;

八、pinia,见之前的博客

Vue3(四) pinia

九、插槽slot

回顾Vue2插槽内容Vue2插槽

1. 默认插槽

将组件标签中的html结构放入slot所在的位置

2. 具名插槽

上边的例子中,游戏列表四个字在英雄联盟等列表的上方,是因为页面结构中<h2>游戏列表</h2>ul的上方。

如果页面结构中<h2>游戏列表</h2>ul的下方。如何仍旧保持这样的页面效果:

采用具名插槽,通过v-slot:插槽名#插槽名 指定要把页面结构放在哪个插槽里。

html 复制代码
 <Category>
   <template #s2>
     <video video :src="videoUrl" controls></video>
   </template>
   <template #s1>
     <h2>今日影视推荐</h2>
   </template>
 </Category>

3. 作用域插槽

子组件Game

html 复制代码
<template>
  <div class="game">
    <h2>游戏列表</h2>
    <!-- 需要传递的值 -->
    <slot :youxi="games" x="hh" y="hello"></slot>
  </div>
</template>

<script setup lang="ts" name="Game">
import { reactive } from 'vue';
let games = reactive([
  { id: 'asgytdfats01', name: '英雄联盟' },
  { id: 'asgytdfats02', name: '王者农药' },
  { id: 'asgytdfats03', name: '红色警戒' },
  { id: 'asgytdfats04', name: '斗罗大陆' }
])
</script>

父组件Father.vue

params包含所有参数:

总结:

  1. 默认插槽:
  2. 具名插槽:
    命名<slot name="xxx"></slot>;
    使用: <template #xxx> <template v-slot:xxx>
  3. 作用域插槽
    数据在子组件身上: <slot :xxx="yyy" :n1="zzz" ></slot>
    父组件使用数据:
    <template v-slot="params"> params接收所有传递过来的数据
    <template v-slot:defalut="params"> 通过插槽名指定接收某个插槽的数据
    <template #defalut="{xxx}"> 通过#插槽名指定接收某个插槽的数据

组件通信总结

相关推荐
珊珊来吃1 分钟前
EXCEL中给某一列数据加上双引号
java·前端·excel
胡西风_foxww28 分钟前
【ES6复习笔记】Spread 扩展运算符(8)
前端·笔记·es6·扩展·运算符·spread
小林爱1 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
跨境商城搭建开发1 小时前
一个服务器可以搭建几个网站?搭建一个网站的流程介绍
运维·服务器·前端·vue.js·mysql·npm·php
hhzz1 小时前
vue前端项目中实现电子签名功能(附完整源码)
前端·javascript·vue.js
秋雨凉人心1 小时前
上传npm包加强
开发语言·前端·javascript·webpack·npm·node.js
JoeChen.2 小时前
PostCSS插件——postcss-pxtorem结合动态调整rem实现字体自适应
javascript·ecmascript·postcss
NoneCoder2 小时前
CSS系列(37)-- Overscroll Behavior详解
前端·css
Nejosi_念旧2 小时前
使用Webpack构建NPM Library
前端·webpack·npm
前端切圖仔2 小时前
失业,仲裁,都赶上了(二)
前端·javascript·程序员