组件之间的通信方式
1、props
props 父组件传数据给子组件 本质上props传参方式只支持父传向子,因为参数传递位置写于标签中,在子组件不存在标签。但是不影响子组件可以调用父组件的setXXX方法来给父组件传递参数。
举例说明:【互送礼物】
父给子玩具(直接传递)
子给父礼物(调用方法)
child.vue
<template>
<div class="child">
<h2>父亲给的玩具:{{ play.name }}</h2>
<input type="text" v-model="gift" />
</div>
<button @click="sendGift(gift)">送父亲礼物</button>
</template>
<script lang="ts" setup name="child">
import { ref, reactive } from 'vue'
defineProps(['play', 'sendGift'])
let gift = ref('领带')
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 20px;
border-radius: 10px;
}
</style>
parent.vue
<template>
<div class="father">
<h2>给儿子的玩具:{{ tools.name }}</h2>
<h2 v-show="gift">收到儿子送的礼物:{{ gift }}</h2>
<child :play="tools" :sendGift="getGift"></child>
</div>
</template>
<script lang="ts" setup name="parent">
import child from './child.vue'
import { ref, reactive } from 'vue'
const tools = reactive({
name: '笔记本电脑',
color: 'blue',
size: 10,
})
let gift = ref('')
function getGift(x: string) {
gift.value = x
console.log(`给父亲的礼物:${x}`)
}
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
</style>
2、ref
ref 子组件给父组件传数据
举例说明:【给儿子零花钱】
子给父需求 (需要零花钱)
父给子解决需求 (给儿子零花钱)
child.vue
<template>
<div class="child">
<h2>儿子给父亲的留言:{{ message }}</h2>
</div>
</template>
<script lang="ts" setup name="child">
import { ref, reactive } from 'vue'
//接收父亲的零花钱
function getMoneyFromParent(money: string) {
console.log('儿子:收到了父亲给的零花钱:' + money + '元')
}
// 儿子给父亲的留言
let message = ref('零花钱用完了,请给我一点零花钱!!!')
// 抛出需求,和方法
defineExpose({ message, getMoneyFromParent })
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 20px;
border-radius: 10px;
}
</style>
parent.vue
<template>
<div class="father">
<child ref="sonMessageAll"></child>
<input type="text" v-model="money" />
<button @click="giveSonMoney(money)">给儿子发红包</button>
</div>
</template>
<script lang="ts" setup name="parent">
import child from './child.vue'
import { ref, reactive } from 'vue'
let sonMessageAll = ref()
let money = ref(10)
setTimeout(() => {
console.log('父亲:你的需求是什么? 儿子的需求(子传父):', sonMessageAll.value.message)
}, 2000)
//给儿子零花钱的方法
function giveSonMoney(newMoney: number) {
//儿子领取零花钱的方法
sonMessageAll.value.getMoneyFromParent(newMoney)
}
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
</style>
3、自定义事件
自定义事件 子组件给父组件传数据
举例说明:【给父母零花钱】
子给父钱花 (告知每月最大金额)
父给子取钱花 (告知取了多少钱)
child.vue
<template>
<div class="child">
<!-- 触发事件,传递给零花钱 -->
<!-- <button @click="emitToParentParams(maxMoney), emitToParent()">给父母零花钱</button> -->
<!-- 点击按钮触发事件分装了 -->
<button @click="handleClick(maxMoney)">给父母零花钱</button>
</div>
</template>
<script lang="ts" setup name="child">
import { ref, reactive } from 'vue'
const emit = defineEmits(['toParent', 'toParentParams'])
//每月最大金额
let maxMoney = ref('1000')
//给父母零花钱
function sendMoneyToParent(money: string) {
console.log('父母取了:' + money + '元')
}
//触发事件1 父传子 告知儿子取了多少钱
function emitToParent() {
emit('toParent', sendMoneyToParent)
}
//触发事件2 子传父 告诉父母最大金额
function emitToParentParams(maxMoney: string) {
emit('toParentParams', maxMoney)
}
// 点击按钮触发事件
function handleClick(maxMoney: string) {
emitToParentParams(maxMoney)
emitToParent()
}
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 20px;
border-radius: 10px;
}
</style>
parent.vue
<template>
<div class="father">
<input type="text" v-model="money" />
<!-- 自定义的@toParent事件 -->
<child @toParent="getMoneyFromSon" @toParentParams="getMaxMoney"></child>
</div>
</template>
<script lang="ts" setup name="parent">
import child from './child.vue'
import { ref, reactive } from 'vue'
let money = ref(0)
//取钱
function getMoneyFromSon(func: Function) {
//调用子组件sendMoneyToParent方法,并传入money参数 (父传子)
func(money.value)
}
//告知最大金额 (子传父)
function getMaxMoney(money: string) {
console.log('最大金额为:' + money)
}
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
</style>
4、mitt
安装mitt (npm i mitt)
mitt 是一个小型的事件管理器,可以用来管理自定义事件,完成任意组件之间的通信
- 接收数据:提前绑定好事件
- 提供数据:在合适的时候触发事件
举例说明:【提醒儿子带雨伞】
父传子 父亲提醒儿子带雨伞 子传父 儿子告诉父亲已经把伞提前准备到门口
child.vue
<template>
<div class="child">
<h2 v-show="msg">接收到父亲的消息:{{ msg }}</h2>
<input type="text" v-model="sonMsg" placeholder="答复父亲..." />
<br />
<button @click="emitMessage(sonMsg)">告诉父亲</button>
</div>
</template>
<script lang="ts" setup name="child">
import { ref, reactive, onUnmounted } from 'vue'
import emitter from '../utils/mittUtils'
//接收父亲的消息
let msg = ref()
//告诉父亲的消息
let sonMsg = ref('')
function emitMessage(message: any) {
emitter.emit('answer-message', message)
}
emitter.on('rain-warning', (message: any) => {
msg.value = message
console.log(message)
})
onUnmounted(() => {
emitter.off('rain-warning')
})
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 20px;
border-radius: 10px;
}
</style>
parent.vue
<template>
<div class="father">
<h2>父亲看完天气预报,预报说今天有雨。</h2>
<button @click="tellSonMessage('父亲:今天将要下雨,记得带伞')">告诉儿子带伞</button>
<h2>收到儿子的答复:{{ fromSonMsg }}</h2>
<!-- 自定义的@toParent事件 -->
<child></child>
</div>
</template>
<script lang="ts" setup name="parent">
import child from './child.vue'
import emitter from '../utils/mittUtils'
import { ref, reactive, onUnmounted } from 'vue'
function tellSonMessage(msg: string) {
emitter.emit('rain-warning', msg)
}
let fromSonMsg = ref('')
emitter.on('answer-message', (message: any) => {
fromSonMsg.value = message
console.log('儿子回答:', message)
})
onUnmounted(() => {
emitter.off('answer-message')
})
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
</style>
5、pinia
pinia 是一个状态管理库,可以用来管理组件的状态,完成任意组件之间的通信
安装pinia (npm i pinia)
1、引入pinia
import { createPinia } from 'pinia'
2、创建pinia实例
const pinia = createPinia()
3、注册pinia实例
app.use(pinia)
可以实现在不同的组件中共享状态,实现任意组件之间的通信
组合式写法
counter.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
//state 参数
const count = ref(1)
//getters 计算属性
const doubleCount = computed(() => count.value * 2)
//actions 方法
function increment() {
count.value++
}
//actions 方法
function incrementBy(amount: number) {
count.value += Number(amount)
}
//返回state、getters、actions
return { count, doubleCount, increment, incrementBy }
})
component.vue
<template>
<div class="father">
<h2>count:{{ count }}</h2>
<h2>doubleCount:{{ doubleCount }}</h2>
<input type="text" v-model="inputIncrement" />
<button @click="counterStore.incrementBy(inputIncrement)">increment</button>
<button @click="test">test</button>
</div>
</template>
<script lang="ts" setup name="parent">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
import { ref } from 'vue'
let inputIncrement = ref(0)
const counterStore = useCounterStore()
const { count, doubleCount } = storeToRefs(counterStore)
function test() {
console.log(inputIncrement.value)
}
// 监控订阅数据变化
counterStore.$subscribe((mutation, state) => {
console.log('from:', mutation.events.oldValue, 'to:', mutation.events.newValue)
console.log(mutation, state)
})
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
</style>
6、provide/inject
provide/inject 是一个依赖注入的机制,可以用来管理组件的状态,完成祖->子->孙组件之间通信,和attrs在祖孙之间传递数据需要经过子组件,而provide/inject在祖孙之间点对点传输数据,不依赖子组件。
使用 Symbol 作为 key,可以避免键值choice,防止冲突。provide用于祖组件提供数据,inject 用于孙组件接收数据,反之会报错,可以调用set方法的方式让子给祖发送数据。
objects.ts
export const mySymbol = Symbol('唯一key')
parent.vue
<template>
<div class="father">
<child></child>
</div>
</template>
<script lang="ts" setup name="parent">
import child from './child.vue'
import { mySymbol } from '@/stores/object'
import { provide } from 'vue'
// 在父组件中提供数据
provide(mySymbol, '给子、孙组件提供的数据')
</script>
<style scoped>
.father {
background-color: rgb(165, 164, 164);
padding: 20px;
border-radius: 10px;
}
</style>
child.vue
<template>
<div class="child">
<h2 v-show="value">接收到祖、父组件的消息:{{ value }}</h2>
</div>
</template>
<script lang="ts" setup name="child">
import { inject } from 'vue'
import { mySymbol } from '@/stores/object'
// 在子组件或孙组件中注入数据
const value = inject(mySymbol)
console.log(value) // 输出: some value
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 20px;
border-radius: 10px;
}
</style>
7、$attrs
defineProps(['a','b','c'])
8、parent
defineExpose({})
9、v-model
本质是defineProps和defineEmits的组合
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
10、默认插槽、具名插槽、作用域插槽
注意:作用域插槽的作用域是父组件,子组件无法修改父组件的作用域插槽内容。 子组件通过props传递数据给父组件,父组件通过插槽渲染子组件。【不需要在父组件中defineProps就能直接接收】