使用频率最高的通信方式,常用于:父 ↔ 子
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <h4>汽车:{{ car }}</h4> <h4 v-show="toy">子组件给的玩具:{{ toy }}</h4> <Child :car="car" :sendToy="getToy" /> </div> </template> <script setup lang="ts" name="Parent"> import Child from './Child.vue' import { ref } from 'vue' // 数据 let car = ref('奔驰') let toy = ref('') // 方法 function getToy(value: string) { toy.value = value } </script> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } </style>
Child.vue
vue<template> <div class="child"> <h3>子组件</h3> <h4>玩具:{{ toy }}</h4> <h4>父组件给的车:{{ car }}</h4> <button @click="sendToy(toy)">把玩具给父组件</button> </div> </template> <script setup lang="ts" name="Child"> import { ref } from 'vue' // 数据 let toy = ref('奥特曼') // 声明接收props defineProps(['car', 'sendToy']) </script> <style scoped> .child { background-color: skyblue; padding: 10px; box-shadow: 0 0 10px black; border-radius: 10px; } </style>
自定义事件常用于:子 → 父
click
、mouseenter
等等)$event
: 是包含事件相关信息的对象(pageX
、pageY
、target
、keyCode
)$event
: 是调用 emit
时所提供的数据,可以是任意类型!!!Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <h4 v-show="toy">子给的玩具:{{ toy }}</h4> <!-- 给子组件Child绑定事件 --> <Child @send-toy="getToy" /> </div> </template> <script setup lang="ts" name="Parent"> import Child from './Child.vue' import { ref } from "vue"; // 数据 let toy = ref('') // 拿到子组件传递过来的玩具 function getToy(value: string) { console.log('saveToy', value) toy.value = value } </script> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } .father button { margin-right: 5px; } </style>
Child.vue
vue<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> <style scoped> .child { margin-top: 10px; background-color: rgb(76, 209, 76); padding: 10px; box-shadow: 0 0 10px black; border-radius: 10px; } </style>
可实现任意组件通信,是一个 pubsub 库,访问 Github
安装 mitt:npm i mitt
@/utils/emitter.ts
typescript// 引入mitt
import mitt from 'mitt'
// 调用mitt得到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
Child1.vue
vue<template> <div class="child1"> <h3>子组件1</h3> <h4>玩具:{{ toy }}</h4> <button @click="emitter.emit('send-toy', toy)">玩具给弟弟</button> </div> </template> <script setup lang="ts" name="Child1"> import { ref } from 'vue' import emitter from '@/utils/emitter'; // 数据 let toy = ref('奥特曼') </script> <style scoped> .child1 { margin-top: 50px; background-color: skyblue; padding: 10px; box-shadow: 0 0 10px black; border-radius: 10px; } .child1 button { margin-right: 10px; } </style>
Child2.vue
vue<template> <div class="child2"> <h3>子组件2</h3> <h4>电脑:{{ computer }}</h4> <h4>哥哥给的玩具:{{ toy }}</h4> </div> </template> <script setup lang="ts" name="Child2"> import { ref, onUnmounted } from 'vue' import emitter from '@/utils/emitter'; // 数据 let computer = ref('华硕') let toy = ref('') // 给emitter绑定send-toy事件 emitter.on('send-toy', (value: any) => { toy.value = value }) // 在组件卸载时解绑send-toy事件 onUnmounted(() => { emitter.off('send-toy') }) </script> <style scoped> .child2 { margin-top: 50px; background-color: orange; padding: 10px; box-shadow: 0 0 10px black; border-radius: 10px; } </style>
UI 组件库大量使用 v-model
进行通信,父 ↔ 子
v-model
用在 html 标签上:
vue<input type="text" v-model="username"> <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value">
v-model
用在组件标签上:
vue<MoraInput v-model="username"/> <MoraInput :modelValue="username" @update:modelValue="username = $event" />
modelValue
可以改名,以便多次使用 v-model
来传多个值
$event
:
- 原生事件:
$event
是事件对象,.target
- 自定义事件:
$event
是触发事件时所传递的数据
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <h4>{{ username }}</h4> <h4>{{ password }}</h4> <MoraInput v-model:ming="username" v-model:mima="password"/> </div> </template> <script setup lang="ts" name="Parent"> import { ref } from "vue"; import MoraInput from './MoraInput.vue' // 数据 let username = ref('zhangsan') let password = ref('123456') </script> <style scoped> .father { padding: 20px; background-color: rgb(165, 164, 164); border-radius: 10px; } </style>
MoraInput.vue
vue<template> <input type="text" :value="ming" @input="emit('update:ming',(<HTMLInputElement>$event.target).value)" > <br> <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> <style scoped> input { border: 2px solid black; background-color: #4158D0; background-image: linear-gradient(43deg, #4158D0 0%, #C850C0 46%, #FFCC70 100%); height: 30px; font-size: 20px; color: white; } </style>
实现当前组件的父组件向当前组件的子组件通信,祖 → 孙
$attrs
是一个对象,包含所有父组件传入的标签属性,会自动过滤 props 中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)
可以类比 props,只是 $attrs
通过子组件,直接传给孙组件,在孙组件中用 defineProps
接收
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <h4>a:{{ a }}</h4> <h4>b:{{ b }}</h4> <h4>c:{{ c }}</h4> <h4>d:{{ d }}</h4> <Child :a="a" :b="b" :c="c" :d="d" v-bind="{ x: 100, y: 200 }" :updateA="updateA" /> </div> </template> <script setup lang="ts" name="Parent"> 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> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } </style>
Child.vue
vue<template> <div class="child"> <h3>子组件</h3> <GrandChild v-bind="$attrs" /> </div> </template> <script setup lang="ts" name="Child"> import GrandChild from './GrandChild.vue' </script> <style scoped> .child { margin-top: 20px; background-color: skyblue; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>
GrandChild.vue
vue<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(6)">点我更新a</button> </div> </template> <script setup lang="ts" name="GrandChild"> defineProps(['a', 'b', 'c', 'd', 'x', 'y', 'updateA']) </script> <style scoped> .grand-child { margin-top: 20px; background-color: orange; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>
$refs
与 $parent
$refs
:子 → 父$parent
:父 → 子属性 | 说明 |
---|---|
$refs | 值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例 |
$parent | 值为对象,当前组件的父组件实例对象 |
需要通过 defineExpose
定义要向外暴露的属性
Parent.vue
vue<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="Parent"> import Child1 from './Child1.vue' import Child2 from './Child2.vue' import { ref, reactive } from "vue"; let c1 = ref() let c2 = ref() // 数据 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> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } .father button { margin-bottom: 10px; margin-left: 10px; } </style>
Child1.vue
vue<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> <style scoped> .child1 { margin-top: 20px; background-color: skyblue; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>
Child2.vue
vue<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> <style scoped> .child2 { margin-top: 20px; background-color: orange; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>
provide
可以向该组件的所有后代组件(子组件、孙组件等)提供数据,需要传递响应式数据,常用于:祖 ↔ 孙
inject
可以拿到 provide
的数据,第一个参数为名称,第二个参数为默认值
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <h4>银子:{{ money }}万元</h4> <h4>车子:一辆{{ car.brand }}车,价值{{ car.price }}万元</h4> <Child /> </div> </template> <script setup lang="ts" name="Parent"> import Child from './Child.vue' import { ref, reactive, provide } from 'vue' let money = ref(100) let car = reactive({ brand: '奔驰', price: 100 }) function updateMoney(value: number) { money.value -= value } // 向后代提供数据 provide('moneyContext', { money, updateMoney }) provide('car', car) </script> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } </style>
GrandChild.vue
vue<template> <div class="grand-child"> <h3>我是孙组件</h3> <h4>银子:{{ money }}</h4> <h4>车子:一辆{{ car.brand }}车,价值{{ car.price }}万元</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: (param: number) => { } }) let car = inject('car', { brand: '未知', price: 0 }) </script> <style scoped> .grand-child { background-color: orange; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px black; } </style>
http://morales.harbinxiaoshi.cn/post/vue-pinia
使用 <slot></slot>
将标签中的内容渲染到组件内
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <div class="content"> <Category> <ul> <li v-for="game in games" :key="game.id">{{ game.name }}</li> </ul> </Category> <Category> <img :src="imageURL" alt=""> </Category> <Category> <video :src="videoURL" controls></video> </Category> </div> </div> </template> <script setup lang="ts" name="Parent"> import { reactive, ref } from 'vue'; import Category from './Category.vue'; let games = reactive([ { id: 'abc01', name: '泰坦陨落' }, { id: 'abc02', name: '极品飞车' }, { id: 'abc03', name: '红色警戒' }, { id: 'abc04', name: '画中世界' } ]); let imageURL = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg'); let videoURL = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4'); </script> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } .content { display: flex; justify-content: space-evenly; } img,video { width: 100%; } </style>
Category.vue
vue<template> <div class="category"> <h2>标题</h2> <slot>默认内容</slot> </div> </template> <script setup lang="ts"> </script> <script lang="ts"> export default { name: 'Category' } </script> <style scoped> .category { width: 200px; height: 300px; background-color: skyblue; border-radius: 10px; box-shadow: 0 0 10px; } h2 { background-color: orange; text-align: center; font-size: 20px; font-weight: 800; } </style>
具有名字的插槽,在 template
或组件上通过 v-slot
来指定,可以用 #
简写,如:v-slot:s1
也可以写为 #s1
默认插槽也有名字,是 default
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <div class="content"> <Category> <template v-slot:title> <h2>游戏列表</h2> </template> <template v-slot:content> <ul> <li v-for="game in games" :key="game.id">{{ game.name }}</li> </ul> </template> </Category> <Category> <template v-slot:title> <h2>今日美食</h2> </template> <template v-slot:content> <img :src="imageURL" alt=""> </template> </Category> <Category> <template v-slot:title> <h2>影视推荐</h2> </template> <template v-slot:content> <video :src="videoURL" controls></video> </template> </Category> </div> </div> </template> <script setup lang="ts" name="Parent"> import { reactive, ref } from 'vue'; import Category from './Category.vue'; let games = reactive([ { id: 'abc01', name: '泰坦陨落' }, { id: 'abc02', name: '极品飞车' }, { id: 'abc03', name: '红色警戒' }, { id: 'abc04', name: '画中世界' } ]); let imageURL = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg'); let videoURL = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4'); </script> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } .content { display: flex; justify-content: space-evenly; } img, video { width: 100%; } h2 { background-color: orange; text-align: center; font-size: 20px; font-weight: 800; } </style>
Category.vue
vue<template> <div class="category"> <h2> <slot name="title">默认标题</slot> </h2> <slot name="content">默认内容</slot> </div> </template> <script setup lang="ts"> </script> <script lang="ts"> export default { name: 'Category' } </script> <style scoped> .category { width: 200px; height: 300px; background-color: skyblue; border-radius: 10px; box-shadow: 0 0 10px; } h2 { background-color: orange; text-align: center; font-size: 20px; font-weight: 800; } </style>
为 <slot></slot>
传递 props,可以在 template 中通过 v-slot
接收,多用于 UI 组件库中
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定
具名作用域插槽:<template v-slot:slotname="params">
或 <template #slotname="params">
Parent.vue
vue<template> <div class="father"> <h3>父组件</h3> <div class="content"> <Game> <template v-slot="params"> <ul> <li v-for="game in params.gameList" :key="game.id"> {{ game.name }} </li> </ul> </template> </Game> <Game> <template v-slot="params"> <ol> <li v-for="game in params.gameList" :key="game.id"> {{ game.name }} </li> </ol> </template> </Game> <Game> <template v-slot="params"> <h3 v-for="game in params.gameList" :key="game.id"> {{ game.name }} </h3> </template> </Game> </div> </div> </template> <script setup lang="ts" name="Parent"> import Game from './Game.vue'; </script> <style scoped> .father { background-color: rgb(165, 164, 164); padding: 20px; border-radius: 10px; } .content { display: flex; justify-content: space-evenly; } img, video { width: 100%; } h2 { background-color: orange; text-align: center; font-size: 20px; font-weight: 800; } </style>
Game.vue
vue<template> <div class="game"> <h2>游戏列表</h2> <slot :gameList="games" x="哈哈" y="你好"></slot> </div> </template> <script setup lang="ts" name="Game"> import { reactive } from 'vue' let games = reactive([ { id: 'abc01', name: '泰坦陨落' }, { id: 'abc02', name: '极品飞车' }, { id: 'abc03', name: '红色警戒' }, { id: 'abc04', name: '画中世界' } ]) </script> <style scoped> .game { width: 200px; height: 300px; background-color: skyblue; border-radius: 10px; box-shadow: 0 0 10px; } h2 { background-color: orange; text-align: center; font-size: 20px; font-weight: 800; } </style>
本文作者:Morales
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!