vue3 组件通信方式

87 阅读2分钟

组件之间的通信方式

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比较类似,区别在于attrs比较类似,区别在于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、refsrefs、parent

defineExpose({})

9、v-model

本质是defineProps和defineEmits的组合

defineProps(['modelValue'])

defineEmits(['update:modelValue'])

10、默认插槽、具名插槽、作用域插槽

注意:作用域插槽的作用域是父组件,子组件无法修改父组件的作用域插槽内容。 子组件通过props传递数据给父组件,父组件通过插槽渲染子组件。【不需要在父组件中defineProps就能直接接收】