Vue3 组件通信和 Vue2 的区别:
- 移出事件总线,使用 mitt 代替
- vuex 换成了 pinia
- .sync 优化到了 v-model 里面了
- $listeners 所有的东西,合并到 $attrs 中了
- $children 被砍掉了
常见搭配形式:
# props
使用频率最高的一种通信方式,常用于 :父 ⇆ 子
- 若 父传子:属性值是非函数
- 若 子传父:属性值是函数
示例
父组件
<template> | |
<div class="father"> | |
<h3>父组件</h3> | |
<div>父组件的值:</div> | |
<div v-show="text">接收:</div> | |
<Child :msg="msg" :sendText="getText" /> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Child from "./Child.vue"; | |
import { ref } from "vue"; | |
let msg = ref("父传子"); | |
let text = ref(""); | |
// 函数接收子组件传来的值 | |
function getText(value: string) { | |
text.value = value; | |
} | |
</script> |
子组件
<template> | |
<div class="child"> | |
<h3>子组件</h3> | |
<div>子组件的值:</div> | |
<div>接收:</div> | |
<button @click="sendText(text)">子传父</button> | |
</div> | |
</template> | |
<script setup lang="ts" name="Child"> | |
import { ref } from "vue"; | |
let text = ref("子传父"); | |
defineProps(["msg", "sendText"]); | |
</script> |
# 自定义事件
自定义事件常用于:子 ➡︎ 父
注意区分好:原生事件、自定义事件
- 原生事件
- 事件名是特定的(
click
、mosueenter
等等) - 事件对象
$event
:是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)
- 事件名是特定的(
- 自定义事件
- 事件名是任意名称
- 事件对象
$event
:是调用emit
时所提供的数据,可以是任意类型!!!
<!-- 在父组件中,给子组件绑定自定义事件:--> | |
<Child @send-toy="toy = $event" /> | |
<!-- 注意区分原生事件与自定义事件中的 $event--> | |
<button @click="toy = $event">测试</button> |
示例
父组件
<template> | |
<div class="father"> | |
<h3>父组件</h3> | |
<!-- 什么都不传 --> | |
<!-- <button @click="test"> 点我 & lt;/button> --> | |
<!-- $event 占位符:事件对象 --> | |
<!-- <button @click="test (6, $event)"> 点我 & lt;/button> --> | |
<div v-show="text">接收:</div> | |
<!-- abc:事件名,xyz:事件回调 | |
<Child @abc="xyz" /> --> | |
<!-- 给子组件绑定事件 --> | |
<Child @send-text="getText" /> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Child from "./Child.vue"; | |
import { ref } from "vue"; | |
// function test(x) { | |
// // 什么都不传,传递的是 event 对象 | |
// console.log ("父组件 test 方法被调用了", x); | |
// } | |
// function test(a: number, x: Event) { | |
// // 什么都不传,传递的是 event 对象 | |
// console.log ("父组件 test 方法被调用了", a, x); | |
// } | |
let text = ref(""); | |
function getText(value: string) { | |
text.value = value; | |
} | |
</script> |
子组件
<template> | |
<div class="child"> | |
<h3>子组件</h3> | |
<div>子组件的值:</div> | |
<button @click="emit('sendText', text)">子传父</button> | |
</div> | |
</template> | |
<script setup lang="ts" name="Child"> | |
import { ref, onMounted } from "vue"; | |
let text = ref("子传父"); | |
// 声明事件 | |
const emit = defineEmits(["sendText"]); | |
onMounted(() => { | |
setTimeout(() => { | |
emit("sendText", "3秒后获取子组件的值"); // 子组件中,触发事件 | |
}, 3000); | |
}); | |
</script> |
# mitt
与消息订阅与发布( pubsub
)功能类似,可以实现任意组件间通信
# 安装 mitt | |
npm i mitt |
基本使用: src\utils\emitter.ts(新建文件)
// 引入 mitt | |
import mitt from "mitt"; | |
// 调用 mitt 得到 emitter,emitter 能:绑定事件、触发事件 | |
// 创建 emitter | |
const emitter = mitt(); | |
/* | |
// 绑定事件 | |
emitter.on ('test1',()=>{ | |
console.log ('test1 被调用了 ') | |
}) | |
emitter.on ('test2',()=>{ | |
console.log ('test2 被调用了 ') | |
}) | |
// 触发事件 | |
setInterval (() => { | |
emitter.emit ('test1') | |
emitter.emit ('test2') | |
}, 1000); | |
// 解绑事件 | |
setTimeout (() => { | |
emitter.off ('test1') | |
emitter.off ('test2') | |
emitter.all.clear () // 清空所有事件 | |
}, 3000); | |
*/ | |
// 暴露 emitter | |
export default emitter; |
示例
接收数据的组件中:绑定事件、同时在销毁前解绑事件
import emitter from "@/utils/emitter"; | |
import { onUnmounted } from "vue"; | |
// 绑定事件 | |
emitter.on("send-toy", value => { | |
console.log("send-toy事件被触发", value); | |
}); | |
onUnmounted(() => { | |
// 解绑事件 | |
emitter.off("send-toy"); | |
}); |
【第三步】:提供数据的组件,在合适的时候触发事件
import emitter from "@/utils/emitter"; | |
function sendToy() { | |
// 触发事件 | |
emitter.emit("send-toy", toy.value); | |
} |
注意这个重要的内置关系,总线依赖着这个内置关系
# v-model
实现 父 ⇆ 子 之间相互通信
1、用在 html 标签上
<input type="text" v-model="username" /> | |
<!-- v-model 底层原理 --> | |
<input | |
type="text" | |
:value="username" | |
@input="username = (<HTMLInputElement>$event.target).value" | |
/> |
2、用在组件标签上
本质: :moldeValue + update:modelValue 事件
<MyInput v-model="username" /> | |
<!-- 组件标签上 v-model 的本质 --> | |
<MyInput :modelValue="username" @update:modelValue="username = $event" /> |
示例:自定义组件
MyInput 组件
<template> | |
<input | |
type="text" | |
:value="modelValue" | |
@input="emits('update:modelValue', (<HTMLInputElement>$event.target).value)" | |
/> | |
</template> | |
<script setup lang="ts" name="MyInput"> | |
// 接收 props | |
defineProps(["modelValue"]); | |
// 声明事件 | |
const emit = defineEmits(["update:model-value"]); | |
</script> |
父组件
<template> | |
<div>用户:</div> | |
<MyInput v-model="username" /> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import { ref } from "vue"; | |
import MyInput from "./MyInput.vue"; | |
let username = ref("daidai"); | |
</script> |
示例:在组件标签上多次使用 v-model
MyInput 组件
<template> | |
<!-- 修改 modelValue --> | |
<input | |
type="text" | |
:value="user" | |
@input="emits('update:user', (<HTMLInputElement>$event.target).value)" | |
/> | |
<input | |
type="text" | |
:value="pwd" | |
@input="emits('update:pwd', (<HTMLInputElement>$event.target).value)" | |
/> | |
</template> | |
<script setup lang="ts" name="MyInput"> | |
// 修改 modelValue | |
defineProps(["user", "pwd"]); | |
const emits = defineEmits(["update:user", "update:pwd"]); | |
</script> |
父组件
<template> | |
<MyInput v-model:user="username" v-model:pwd="password" /> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import { ref } from "vue"; | |
import MyInput from "./MyInput.vue"; | |
let username = ref("daidai"); | |
let password = ref("123456"); | |
</script> |
# $attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖 ➡︎ 孙)
$attrs
是一个对象,包含所有父组件传入的标签属性
⚠️ 注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己 “消费” 了)
示例
父组件
<template> | |
<div class="father"> | |
<h3>父组件</h3> | |
<div>a: ~~ b: ~~ c: ~~ d:</div> | |
<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: number) { | |
a.value += value; | |
} | |
</script> |
子组件
<template> | |
<div class="child"> | |
<h3>子组件</h3> | |
<div>a:</div> | |
<div>未接收:</div> | |
<GrandChild v-bind="$attrs" /> | |
</div> | |
</template> | |
<script setup lang="ts" name="Child"> | |
import GrandChild from "./GrandChild.vue"; | |
defineProps(["a"]); | |
</script> |
孙组件
<template> | |
<div class="grand-child"> | |
<h3>孙子组件</h3> | |
<div>b: ~~ c: ~~ d: ~~ x: ~~ y:</div> | |
<button @click="updateA(6)">点我:a+6</button> | |
</div> | |
</template> | |
<script setup lang="ts" name="GrandChild"> | |
defineProps(["b", "c", "d", "x", "y", "updateA"]); | |
</script> |
# parent
- $refs:父 ➡︎ 子
- $parent:子 ➡︎ 父
原理:
属性 | 说明 |
---|---|
$refs | 值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例 |
$parent | 值为对象,当前组件的父组件实例对象 |
示例
父组件
<template> | |
<div class="father"> | |
<h3>父组件</h3> | |
<div>父组件的值:</div> | |
<button @click="changeText1">修改子组件1的值</button> | |
<button @click="changeText2">修改子组件2的值</button> | |
<button @click="getAllChild($refs)">获取所有子组件实例对象并修改</button> | |
<Child1 ref="child1" /> | |
<Child2 ref="child2" /> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Child1 from "./Child1.vue"; | |
import Child2 from "./Child2.vue"; | |
import { ref } from "vue"; | |
let msg = ref("我是父"); | |
let child1 = ref(); | |
let child2 = ref(); | |
function changeText1() { | |
child1.value.text = "父组件修改成功"; | |
} | |
function changeText2() { | |
child2.value.text = "父组件修改成功"; | |
} | |
// refs: { [key: string]: any } | |
function getAllChild(refs: any) { | |
for (let key in refs) { | |
refs[key].text = "父组件修改成功"; | |
} | |
} | |
// 向外部提供数据 | |
defineExpose({ msg }); | |
</script> |
子组件 1
<template> | |
<div class="child1"> | |
<h3>子组件1</h3> | |
<div>子组件的值:</div> | |
<button @click="changeMsg($parent)">修改父组件的值</button> | |
</div> | |
</template> | |
<script setup lang="ts" name="Child1"> | |
import { ref } from "vue"; | |
let text = ref("我是子1"); | |
function changeMsg(parent: any) { | |
parent.msg = "子组件1修改成功"; | |
} | |
// 向外部提供数据 | |
defineExpose({ text }); | |
</script> |
子组件 2
<template> | |
<div class="child2"> | |
<h3>子组件2</h3> | |
<div>子组件的值:</div> | |
</div> | |
</template> | |
<script setup lang="ts" name="Child2"> | |
import { ref } from "vue"; | |
let text = ref("我是子2"); | |
defineExpose({ text }); | |
</script> |
# provide、inject
实现祖孙组件直接通信
具体使用:
- 在祖先组件中通过
provide
配置向后代组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
⚠️ 注:子组件中不用编写任何东西,是不受到任何打扰的
示例
父组件:使用 provide
提供数据
<template> | |
<div class="father"> | |
<h3>父组件</h3> | |
<div>父组件的值:</div> | |
<div>我是, 今年岁</div> | |
<Child /> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Child from "./Child.vue"; | |
import { ref, reactive, provide } from "vue"; | |
let msg = ref("我是父组件"); | |
let person = reactive({ | |
name: "daidai", | |
age: 18 | |
}); | |
function updateMsg(value: string) { | |
msg.value += value; | |
} | |
// 向后代提供数据 | |
provide("msgContext", { msg, updateMsg }); | |
provide("person", person); | |
</script> |
孙组件:使用 inject
接受数据
<template> | |
<div class="grand-child"> | |
<h3>孙子组件</h3> | |
<button @click="updateMsg('~')">修改父组件</button> | |
<div>接收:</div> | |
<div>我是, 今年岁</div> | |
</div> | |
</template> | |
<script setup lang="ts" name="GrandChild"> | |
import { inject } from "vue"; | |
let { msg, updateMsg } = inject("msgContext", { | |
msg: "默认值", | |
updateMsg: (params: string) => {} | |
}); | |
let person = inject("person", { name: "默认", age: 0 }); | |
</script> |
# pinia
参考 pinia 部分的讲解
# slot
# 默认插槽
示例
子组件
<template> | |
<div class="category"> | |
<h2>组件通信</h2> | |
<slot>默认内容</slot> | |
</div> | |
</template> | |
<script setup lang="ts" name="Category"> | |
defineProps(["title"]); | |
</script> |
父组件
<template> | |
<div class="father"> | |
<h3>父组件 - 默认插槽</h3> | |
<div class="main"> | |
<Category title="热门游戏列表"> | |
<ul> | |
<li v-for="item in games" :key="item.id"></li> | |
</ul> | |
</Category> | |
<Category title="今日美食城市"> | |
<img :src="imgUrl" alt="" /> | |
</Category> | |
<Category title="今日影视推荐"> | |
<video :src="videoUrl" controls></video> | |
</Category> | |
</div> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Category from "./Category.vue"; | |
import { ref, reactive } from "vue"; | |
let games = reactive([ | |
{ id: 1, name: "蛋仔派对" }, | |
{ id: 2, name: "开心消消乐" }, | |
{ id: 3, name: "王者荣耀" }, | |
{ id: 4, name: "英雄联盟" } | |
]); | |
let imgUrl = ref("https://z1.ax1x.com/2023/11/19/piNxLo4.jpg"); | |
let videoUrl = ref("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"); | |
</script> |
# 具名插槽
示例
子组件
<template> | |
<div class="category"> | |
<slot name="title">默认内容1</slot> | |
<slot name="content">默认内容2</slot> | |
</div> | |
</template> |
父组件
<template> | |
<div class="father"> | |
<h3>父组件 - 具名插槽</h3> | |
<div class="main"> | |
<Category> | |
<template v-slot:content> | |
<ul> | |
<li v-for="item in games" :key="item.id"></li> | |
</ul> | |
</template> | |
<template v-slot:title> | |
<h2>热门游戏列表</h2> | |
</template> | |
</Category> | |
<Category> | |
<template v-slot:content> | |
<img :src="imgUrl" alt="" /> | |
</template> | |
<template v-slot:title> | |
<h2>今日美食城市</h2> | |
</template> | |
</Category> | |
<Category> | |
<template #content> | |
<video video :src="videoUrl" controls></video> | |
</template> | |
<template #title> | |
<h2>今日影视推荐</h2> | |
</template> | |
</Category> | |
</div> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Category from "./Category.vue"; | |
import { ref, reactive } from "vue"; | |
let games = reactive([ | |
{ id: 1, name: "蛋仔派对" }, | |
{ id: 2, name: "开心消消乐" }, | |
{ id: 3, name: "王者荣耀" }, | |
{ id: 4, name: "英雄联盟" } | |
]); | |
let imgUrl = ref("https://z1.ax1x.com/2023/11/19/piNxLo4.jpg"); | |
let videoUrl = ref("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"); | |
</script> |
# 作用域插槽
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(新闻数据在 News
组件中,但使用数据所遍历出来的结构由 App
组件决定)
示例
子组件
<template> | |
<div class="game"> | |
<h2>游戏列表</h2> | |
<slot :youxi="games" x="哈哈" y="你好"></slot> | |
</div> | |
</template> | |
<script setup lang="ts" name="Game"> | |
import { reactive } from "vue"; | |
let games = reactive([ | |
{ id: 1, name: "蛋仔派对" }, | |
{ id: 2, name: "开心消消乐" }, | |
{ id: 3, name: "王者荣耀" }, | |
{ id: 4, name: "英雄联盟" } | |
]); | |
</script> |
父组件
<template> | |
<div class="father"> | |
<h3>父组件 - 作用域插槽</h3> | |
<div class="main"> | |
<Game> | |
<!-- params 所有传值打包成对象传过来 --> | |
<template v-slot="params"> | |
<ul> | |
<li v-for="item in params.youxi" :key="item.id"></li> | |
</ul> | |
<div></div> | |
<div></div> | |
</template> | |
</Game> | |
<Game> | |
<template v-slot="params"> | |
<ol> | |
<li v-for="item in params.youxi" :key="item.id"></li> | |
</ol> | |
</template> | |
</Game> | |
<Game> | |
<template #default="{ youxi, x, y }"> | |
<h3 v-for="item in youxi" :key="item.id"></h3> | |
<div></div> | |
<div></div> | |
</template> | |
</Game> | |
</div> | |
</div> | |
</template> | |
<script setup lang="ts" name="Father"> | |
import Game from "./Game.vue"; | |
</script> |