vue3组件通信方式
props【主要用于父给子传数据】
props 可以实现父给子传递数据。
如果要用props实现子给父传递数据,那么父组件要先传递一个函数给子组件(sendToy),子组件再以传参的形式将数据传递给父组件。
Father.vue 父组件
<template>
<div class="father">
<h3>父组件</h3>
<h4>汽车:{{ car }}</h4>
<h4 v-show="toy">子传递过来的玩具:{{ toy }}</h4>
// 将sendToy函数传递给子组件
<Child :car="car" :sendToy="getToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
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 子组件
<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>
自定义事件【主要用于子给父传递数据】
首先给子组件绑定自定义事件(send-toy),在子组件中声明事件,当子组件触发该自定义事件时,就会调用回调函数(saveToy)。
defineEmits用于在setup中注册自定义事件
父组件
<template>
<div class="father">
<h3>父组件</h3>
<h4 v-show="toy">子传递过来的玩具:{{ toy }}</h4>
<!-- 给子组件绑定自定义事件 send-toy -->
<Child @send-toy="saveToy" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from 'vue'
let toy = ref('')
// 用于保存传递过来的玩具
function saveToy(value: string) {
toy.value = value
}
</script>
子组件
<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>
mitt【用于任意组件间通信】
首先下载mitt:npm i mitt
创建utils文件夹,建立emitter.ts文件
import mitt from 'mitt'
// 调用 mitt得到 emitter,emitter能绑定事件,触发事件
const emitter = mitt()
export default emitter
如果子组件1要给子组件2传递数据【子组件1====>子组件2】,
就给子组件2绑定事件【emitter.on(‘事件名’)】,
子组件1触发该事件并传递数据【emitter.emit(‘事件名’, 要传递的数据)】
子组件1
<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>
子组件2
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4 v-show="toy">得到了哥哥传递过来的玩具:{{ 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>
v-model【父传子,子传父】
v-model既可以用在html标签上,用于双向数据绑定,也可以用在组件标签上。
父组件
<template>
<div class="father">
<h3>父组件</h3>
<!-- v-model用在html标签上 -->
<!-- <input type="text" v-model="username" /> -->
<!-- <input type="text" :value="username" @input="username = <HTMLInputElement>($event.target).value" /> -->
<!-- v-model用在组件标签上 -->
<AtguiguInput v-model="username"/>
<!-- <AtguiguInput
:modelValue="username"
@update:modelValue="username = $event" /> -->
</div>
</template>
<script setup lang="ts" name="Father">
import { ref } from 'vue'
import AtguiguInput from './AtguiguInput.vue'
// 数据
let username = ref('zhansan')
</script>
子组件
<template>
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)" />
</template>
<script setup lang="ts" name="AtguiguInput">
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
修改modelValue
父组件
<!-- 修改 modelValue-->
<AtguiguInput v-model:qwe="username" v-model:mima="password"/>
子组件
<template>
<input
type="text"
:value="qwe"
@input="emit('update:qwe', $event.target.value)" />
<br>
<input
type="text"
:value="mima"
@input="emit('update:mima', $event.target.value)" />
</template>
<script setup lang="ts" name="AtguiguInput">
defineProps(['qwe', 'mima'])
const emit = defineEmits(['update:qwe', 'update:mima'])
</script>
对于原生事件,$event 就是事件对象,可以 $event.target
对于自定义事件, $event 就是触发事件时,所传递的数据,不能 $event.target
$attrs【用于祖传孙】
父组件将数据传给子组件,子组件利用 $attrs 将数据全部传给孙组件(<GrandChild v-bind=" $attrs"/ >),孙组件利用defineProps接收传递过来的数据。【需要中间人】
父组件
<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="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>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件
<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(5)">点我更新爷爷传过来的a</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a', 'b', 'c', 'd', 'x', 'y', 'updateA'])
</script>
$refs【父传子】 $parent【子传父】
$refs 包含父组件所有的孩子
$parent 可以得到子组件的父亲
使用 $refs 和 $parent 都需要子组件或父组件向外部提供数据【defineExpose】
父组件
<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="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref} 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:any) {
for (let key in refs) {
// console.log(refs[key])
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({house})
</script>
子组件1
<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>
子组件2
<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>
provide-inject【祖孙直接通信】
父组件利用provide直接向后代传递数据(所有后代都能使用),子组件或者孙组件利用inject接收数据。(不需要中间人)
父组件
<template>
<div class="father">
<h3>父组件</h3>
<h4>金钱:{{ money }}</h4>
<h4>车子:{{ car.brand }},价格:{{ car.price }}</h4>
<Child />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref, reactive, provide } from 'vue'
let money = ref(100)
let car = reactive({
brand: '奔驰',
price: 200,
})
function updateMoney(value: number) {
money.value -= value
}
// provide 第一个参数:参数的名字;第二个参数:参数的值
// 向后代提供数据(所有后代都可以使用)
provide('moneyContext', {money, updateMoney})
provide('car', car)
</script>
孙组件
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>得到的钱:{{ money }}</h4>
<h4>得到的车:{{ car.brand }},价值{{ car.price }}</h4>
<button @click="updateMoney(5)">花爷爷的钱</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
import { inject } from 'vue'
let {money, updateMoney} = inject('moneyContext', {money:0, updateMoney:(x:number)=>{}})
let car = inject('car', { brand: '未知', price: 0 })
</script>
pinia
插槽
默认插槽
默认插槽是将父组件的结构和数据插入子组件中,默认插槽只有一个插入位置。父组件决定结构和数据
父组件
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category title="热门游戏列表">
<ul>
<li v-for="game in games" :key="game.id">
{{ game.name }}
</li>
</ul>
</Category>
<Category title="今日美食城市">
<img :src="imgUrl" />
</Category>
<Category title="今日影视推荐">
<video :src="videoUrl"></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: 'asgytdfatso1', name: '英雄联盟' },
{ id: 'asgytdfatse2', name: '王者农药' },
{ id: 'asgytdfatso3', name: '红色警戒' },
{ id: 'asgytdfatse4', 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">
<h2>{{ title }}</h2>
<slot>默认内容</slot>
</div>
</template>
<script setup lang="ts" name="Category">
defineProps(['title'])
</script>
具名插槽
具名插槽简单地说就是具有名字的插槽,只是默认插槽只有一个插入位置,具名插槽可以有多个插入位置,根据名字来识别对应的插槽。父组件决定结构和数据。
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category>
<template v-slot:s2>
<ul>
<li v-for="game in games" :key="game.id">
{{ game.name }}
</li>
</ul>
</template>
<template v-slot:s1>
<h2>热门游戏列表</h2>
</template>
</Category>
<Category>
<template v-slot:s2>
<img :src="imgUrl" />
</template>
<template v-slot:s1>
<h2>今日美食城市</h2>
</template>
</Category>
<Category>
<template #s2>
<video :src="videoUrl"></video>
</template>
<template #s1>
<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: 'asgytdfatso1', name: '英雄联盟' },
{ id: 'asgytdfatse2', name: '王者农药' },
{ id: 'asgytdfatso3', name: '红色警戒' },
{ id: 'asgytdfatse4', 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="s1">默认内容</slot>
<slot name="s2">默认内容</slot>
</div>
</template>
<script setup lang="ts" name="Category">
</script>
作用域插槽
当数据在子组件中,但是插槽中渲染数据的节点由父组件决定,这种情况下,就需要使用作用域插槽。
可以叫做带数据的插槽,父组件在使用的时候可以替换slot插槽中的显示页面结构,但展示的数据还是来源于子组件。
子组件决定数据,父组件决定结构。
父组件
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Game>
<template v-slot="params">
<ul>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
</Game>
<Game>
<template v-slot="params">
<ol>
<li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
</ol>
</template>
</Game>
<Game>
<!-- 作用域插槽也可以有名字 -->
<template v-slot:default="{ games }">
<h4 v-for="g in games" :key="g.id">{{ g.name }}</h4>
</template>
</Game>
</div>
</div>
</template>
<script setup lang="ts" name="Father">
import Game from './Game.vue'
</script>
子组件
<template>
<div class="game">
<h2>游戏列表</h2>
<slot :games="games"></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>