2024-02-27
Vue
0
请注意,本文编写于 82 天前,最后修改于 79 天前,其中某些信息可能已经过时。

目录

props
自定义事件
mitt
v-model
$attrs
$refs 与 $parent
provide 与 inject
pinia
slot
默认插槽
具名插槽
作用域插槽

props

使用频率最高的通信方式,常用于:父 ↔ 子

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>

1.gif

自定义事件

自定义事件常用于:子 → 父

  • 原生事件:
    • 事件名是特定的(clickmouseenter 等等)
    • 事件对象 $event: 是包含事件相关信息的对象(pageXpageYtargetkeyCode
  • 自定义事件:
    • 事件名是任意名称
    • 事件对象 $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>

2.gif

mitt

可实现任意组件通信,是一个 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>

3.gif

v-model

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>

4.gif

$attrs

实现当前组件的父组件向当前组件的子组件通信,祖 → 孙

$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>

5.gif

$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>

6.gif

provide 与 inject

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>

7.gif

pinia

http://morales.harbinxiaoshi.cn/post/vue-pinia

slot

默认插槽

使用 <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>

image-20240301201303968.png

具名插槽

具有名字的插槽,在 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>

image-20240301202344782.png

作用域插槽

<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>

image-20240301204219647.png

本文作者:Morales

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!