文章目录
- 一、props
- 二、自定义事件
- 三、mitt
- 四、v-model
- 1.`v-model`的本质
- 2.`v-model`用在组件标签上
- 3.更换`modelValue`
- 4.更换`modelValue`时,可以在组件标签上多次使用`v-model`
- 五、$attrs
- 六、$refs,与¥parent
- 1. 回顾标签ref属性修改组件信息
- 2. $refs实现父修改所有子组件信息
- 3. $parent实现子修改父
- 七、provide、inject祖孙通信
- 1. 祖传孙
- 2. 孙传祖
- 八、pinia,见之前的博客
- 九、插槽slot
- 1. 默认插槽
- 2. 具名插槽
- 3. 作用域插槽
- 组件通信总结
一、props
概述:props
是使用频率最高的一种通信方式,常用与 :父传子。
- 若 父传子:属性值是非函数。
- 若 子传父:属性值是函数。
父传子
<!--父组件-->
<Child :car="car" />
<script>let car = ref('奔驰')
</script><!--子组件接收数据--><h4>父亲给的车:{{ car }}</h4><script>defineProps(['car'])
</script>
子传父:
需要父提前给子传递函数,通过参数接收子传递的数据
<!--父组件--><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>
二、自定义事件
触发事件的语法
:
let emit = defineEmits(['函数名'])// 触发事件emit('函数名',需要传递的参数)
举例:
<!--父组件自定义事件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
接收两个事件:
let emit = defineEmits(['a','b'])
触发时:
emit('a')
emit('b')
三、mitt
消息订阅pubsub,全局事件总线$bus,mitt都是一样的思想。
接收数据的:提前绑好事件(提前订阅消息)
提供数据的:在合适的时候触发事件(发布消息)
- 安装mitt:
npm i mitt
- 新建文件:
src\utils\emitter.ts
// 引入mitt import mitt from "mitt";// 创建emitter const emitter = mitt()// 创建并暴露mitt export default emitter
绑定事件:
emitter.on(事件名,()=>{回调函数逻辑 })
触发事件:emitter.emit(事件名,传递的参数)
解绑事件:emitter.off(事件名)
解绑所有事件:emitter.all.clear()
- 接收数据的组件绑定事件+在组件卸载前解绑事件
- 发送数据的组件触发事件
四、v-model
UI 组件库大量使用v-model
进行通信。
1.v-model
的本质
<!-- 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
用在组件标签上
<!-- 修改modelValue -->
<ChildInput v-model="username" />
<!-- 本质是:默认写modelValue,也可以修改,修改见第3点更换modelValue -->
<ChildInput :modelValue="username" @update:modelValue="username = $event" />
:modelValue="username"
:props实现父传子
@update...
:类似于自定义事件,实现子传父;$event
是子组件传过来的值
ChildInput
组件中:
<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
后加上别名
<!-- 修改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
- 对于原生事件,$event就是事件对象,====>能.target
- 对于自定义事件,$event就是触发事件时,所传递的数据,===>不能.target
五、$attrs
- 用来实现祖孙组件之间的通信。
- 当父组件向子组件通过props传值时,子组件``defineProps()只接收部分值,剩下没接收的值则存在了
$attrs
里。
1. 未读取的数据存在$attrs
里
<!-- 父组件 -->
<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. 祖向孙传数据
<!--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传递函数
<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
<!--Child.vue,中间组件啥也不接受,传给GrandChild-->
<GrandChild v-bind="$attrs" />
GrandChild.vue
:调用函数
<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
<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
结构类似:
...
<script setup lang="ts" name="Child2">
...
let computer = ref('联想')
let book = ref(6)
// 向外暴露
defineExpose({ computer, book })
</script>
父组件Father.vue
修改子组件信息
<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
:
<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
将数据暴露出去,子组件才能获取到该属性并修改:
let house = ref(4)
defineExpose({ house })
子组件:Child1.vue
<button @click="minusHouse($parent)">卖出一套房产</button>
<script>function minusHouse(parent: any) {console.log('parent',parent);parent.house -= 1}
</script>
这里读取数据不用写.value
见之前的记录。
七、provide、inject祖孙通信
$attrs
实现祖孙通信了,打扰了中间层;而provide
与inject
不会打扰中间层。
提供数据:provide('数据或方法',数据或方法)
;
接收(注入)数据:inject('数据')
1. 祖传孙
祖组件:Father.vue
<script setup lang="ts" name="Father">let car = ref('奔驰')// 传递的数据,通过provide提供provide('car', car)
</script>
孙组件:GrandChild.vue
<div class="grand-child"><h3>我是孙组件</h3><h4>车子:{{ car }}</h4></div>
<script setup lang="ts" name="GrandChild">// 1.引入injectimport { inject } from 'vue'// 2. 读取数据;let car = inject('car')
</script>
注意,如果接收数据时
let car = inject('car2')
祖组件没有提供名为car2
的数据,则孙组件读取到的car值是undefined
;
孙组件可设置默认值
let car = inject('car2','奥迪') // car的默认值是奥迪
2. 孙传祖
思想还是祖给孙传递函数,孙通过参数的方式来给祖传信息。
祖组件:Father.vue
<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
<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
?
provide('moneyContext', { money.value, spendMoney })
如果传递.value
,则传递的只是一个值;
八、pinia,见之前的博客
Vue3(四) pinia
九、插槽slot
回顾Vue2插槽内容Vue2插槽
1. 默认插槽
将组件标签中的html结构放入slot
所在的位置
2. 具名插槽
上边的例子中,游戏列表
四个字在英雄联盟
等列表的上方,是因为页面结构中<h2>游戏列表</h2>
在ul
的上方。
如果页面结构中<h2>游戏列表</h2>
在ul
的下方。如何仍旧保持这样的页面效果:
采用具名插槽,通过v-slot:插槽名
或#插槽名
指定要把页面结构放在哪个插槽里。
<Category><template #s2><video video :src="videoUrl" controls></video></template><template #s1><h2>今日影视推荐</h2></template></Category>
3. 作用域插槽
子组件Game
<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
包含所有参数:
总结:
- 默认插槽:
- 具名插槽:
命名<slot name="xxx"></slot>
;
使用:<template #xxx>
或<template v-slot:xxx>
- 作用域插槽
数据在子组件身上:<slot :xxx="yyy" :n1="zzz" ></slot>
父组件使用数据:
<template v-slot="params">
params接收所有传递过来的数据
<template v-slot:defalut="params">
通过插槽名指定接收某个插槽的数据
<template #defalut="{xxx}">
通过#插槽名指定接收某个插槽的数据