vue3 组件间通信

290 阅读4分钟

一、props父子传递数据

父传子:传属性值 :money="childMoney"

子传父:属性值是函数 :getFood="getFood"

子接收:defineProps(['money', 'getFood'])

image.png

image.png

父组件 parents.vue

<template>
  <div class="parents">
    <h3>我是父组件</h3>
    <p>爸爸有{{ money }}块钱</p>
    <p>儿子给的:{{ lt }}</p>
    <button @click="giveChildMoney">点击给儿子10元</button>
    <child :money="childMoney" :getFood="getFood" />
  </div>
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue'
import child from '@/components/child.vue'
let money = ref(100)
let childMoney = 0
let lt = ref('')
function giveChildMoney() {
  money.value -= 10
  childMoney += 10
}

function getFood(val: string) {
  lt.value = val
}
</script>

<style scoped>
h3 {
  font-weight: bold;
}
button {
  margin-top: 16px;
  padding: 6px 10px;
  border: 1px solid #6b85f8;
  background-color: #6b85f8;
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
}
.parents {
  background: rgb(211, 241, 253);
  box-shadow: 0 0 10px 0 rgba(211, 241, 253, 1);
  border-radius: 8px;
  padding: 20px;
  width: 500px;
}
</style>

子组件:child.vue

<template>
  <div class="child">
    <h3>我是子组件</h3>
    <p>爸爸给了我{{ money }}块钱让我买好吃的,嘿嘿~</p>
    <p>我买了辣条、棒棒糖、果冻</p>
    <button>把辣条给爸爸</button>
    <button @click="giveParents()">把辣条给爸爸</button>
  </div>
</template>
<script setup lang="ts">
let props = defineProps(['money', 'getFood'])
function giveParents () {
  props.getFood('辣条')
}
</script>
<style scoped>
h3 {
  margin-bottom: 8px;
  font-weight: bold;
}
.child {
  padding: 20px;
  background-color: bisque;
  border-radius: 8px;
  margin-top: 16px;
}
button {
  margin-top: 16px;
  padding: 6px 10px;
  border: 1px solid #f8a202;
  background-color: #fcab14;
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
}
</style>

二、defineEmits 自定义事件(子传父)

用于子传父 defineEmits(['send-food'])

注意:自定义事件官方推荐以“-”分隔

image.png

父组件 parents.vue

<template>
  <div class="parents">
    <h3>我是父组件</h3>
    <p>爸爸有{{ money }}块钱</p>
    <p>儿子给的:{{ lt }}</p>
    <button @click="giveChildMoney">点击给儿子10元</button>
    <child :money="childMoney" @send-food="getFood" />
  </div>
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue'
import child from '@/components/child.vue'
let money = ref(100)
let childMoney = 0
let lt = ref('')
function giveChildMoney() {
  money.value -= 10
  childMoney += 10
}

function getFood(val: string) {
  lt.value = val
}
</script>


子组件

<template>
  <div class="child">
    <h3>我是子组件</h3>
    <p>爸爸给了我{{ money }}块钱让我买好吃的,嘿嘿~</p>
    <p>我买了辣条、棒棒糖、果冻</p>
    <button @click="giveParents()">把辣条给爸爸</button>
  </div>
</template>
<script setup lang="ts">
let props = defineProps(['money'])
let emit = defineEmits(['send-food'])
function giveParents () {
  emit('send-food', '辣条')
}
</script>


三、mitt 任意组件通讯

与$bus和pubsub功能相似,可以实现任意组件通讯

mitt具有以下优点:

  1. 零依赖、体积超小,压缩后只有200b
  2. 提供了完整的typescript支持,能自动推导出参数类型。
  3. 基于闭包实现,没有烦人的this困扰。
  4. 为浏览器编写但也支持其它javascript运行时,浏览器支持ie9+(需要引入Mappolyfill)。 5.与框架无关,可以与任何框架搭配使用。

安装mitt:npm install mitt

utils/emitter.ts

import mitt from "mitt";
// 绑事件 触发事件
const emitter=mitt()
// 暴露事件
export default emitter

/* 

// 触发事件
setTimeout(()=>{
    emitter.emit('test1',"内容数据")
},3000)

// 获取事件
emitter.on('test1',()=>{
  console.log('test1被绑定了');
  
})

// 解绑事件
setTimeout(()=>{
    emitter.off('test1')
    // 清空事件
    // emitter.all.clear();
}) */


在main.ts中全局引入,或者在使用的组件中引入

// main.ts
import emitter from './utils/emitter'
// 挂载在全局上
app.config.globalProperties.$emitter = emitter

// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
  export interface ComponentCustomProperties {
    $emitter: typeof emitter;
  }
} 


// 在组件使用
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance(); 
instance?.proxy?.$emitter.on("foo", () => { // dosomething });


组件中引入(非全局引入)

父组件

<template>
  <div class="parents">
    <h3>我是父组件</h3>
    <p>爸爸有{{ money }}块钱</p>
    <p>儿子给的:{{ lt }}</p>
    <button @click="giveChildMoney">点击给儿子10元</button>
    <child />
  </div>
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue'
import child from '@/components/child.vue'
import emitter from '@/utils/emitter'
let money = ref(100)
let childMoney = 0
let lt = ref('')
function giveChildMoney() {
  money.value -= 10
  childMoney += 10
  //  向child组件传值
  emitter.emit('send-money', childMoney)
}
// 接收child组件传的值
emitter.on('send-food', val => {
  lt.value = val as string
})
</script>

子组件

<template>
  <div class="child">
    <h3>我是子组件</h3>
    <p>爸爸给了我{{ money }}块钱让我买好吃的,嘿嘿~</p>
    <p>我买了辣条、棒棒糖、果冻</p>
    <button @click="giveParents()">把辣条给爸爸</button>
  </div>
</template>
<script setup lang="ts">
import emitter from '@/utils/emitter'
import { ref } from 'vue'
let money = ref(0)
// 向其他组件传值
function giveParents() {
  emitter.emit('send-food', '辣条')
}
// 接收其他组件传递值
emitter.on('send-money', val => {
  money.value = val as number
})
</script>

四、 provide和inject (祖->孙、父->子直接传值)

实现祖孙组件间的直接通讯

在祖先组件中通过provide(名字,内容)向后代组件传值,不要放到方法中使用provide,会报警告

在后代组件中通过inject('名字')获取数据

image.png 父组件

<template>
  <div class="parents">
    <h3>我是父组件</h3>
    <p>爸爸有{{ money }}块钱</p>
    <p>儿子给的:{{ lt }}</p>
    <button @click="giveChildMoney">点击给儿子10元</button>
    <child />
  </div>
</template>

<script lang="ts" setup name="Person">
import { provide, ref } from 'vue'
import child from '@/components/child.vue'

let money = ref(100)
let childMoney = ref(0)
let lt = ref('')
function giveChildMoney () {
  money.value -= 10
  childMoney.value += 10
}
provide('childMoney', childMoney)
</script>

子组件


<template>
  <div class="child">
    <h3>我是子组件</h3>
    <p>爸爸给了我{{ money }}块钱让我买好吃的,嘿嘿~</p>
  </div>
</template>
<script setup lang="ts">
import { inject, ref } from 'vue'
let money = inject('childMoney')
</script>

五、pinia全局变量

安装pinia包 npm install pinia

在main.ts引入

import { createApp } from 'vue' 
import App from './App.vue'
/* 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'
/* 创建pinia */ 
const pinia = createPinia() 
const app = createApp(App) 
/* 使用插件 */
app.use(pinia) 
app.mount('#app')

  1. Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。
  2. 它有三个概念:stategetteraction,相当于组件中的的:data``computed``methods。 stores/count.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
 state:()=>{
   return {
    count:1,
    school:"超星",
    address:"留山大街"
   }
 },
 getters:{
  doubleCount:(state)=>state.count*2
 },
 actions:{
  increment(n:number){
    if(this.count<10){
      this.count+=n
    }
  }
 }
 })

setup式写法

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

 export const useCounterStore = defineStore('counter', () => {

  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
}) 

调用count.vue

<template>
  <div>求和数字:{{ counterStore.count }}</div>
  <button @click="add"></button>
</template>
<script lang="ts" setup name="count">
import { useCounterStore } from '@/stores/counter'
import { ref } from 'vue'
let counterStore = useCounterStore()
let n = ref(2)
function add () {
  /* 改变store的值 */
  // 第一种方式
  // counterStore.count++
  // counterStore.school = '北京'

  // 第二种方式
  /*  counterStore.$patch({
    count: 9,
    school: '上地'
  }) */

  // 第三种
  counterStore.increment(n.value)
}

//监控pinia数据变化
counterStore.$subscribe((mutate, state) => {
  // counterStore 内容, state改变后的数据
  console.log('数据发生变化', mutate, state)
})
</script>

storeToRefs

  • 借助storeToRefsstore中的数据转为ref对象,方便在模板中使用。
  • 注意:pinia提供的storeToRefs只会将数据做转换,而VuetoRefs会转换store中数据。
<template> 
    <div class="count"> 
       <h2>当前求和为:{{sum}}</h2>
     </div> 
 </template>
  <script setup lang="ts" name="Count"> 
      import { useCountStore } from '@/store/count' 
      /* 引入storeToRefs */
      import { storeToRefs } from 'pinia' 
      /* 得到countStore */ 
      const countStore = useCountStore() 
      /* 使用storeToRefs转换countStore,随后解构 */ 
      const {sum} = storeToRefs(countStore) 
  </script>

六、$attrs透传

向当前组件子组件通信(祖->孙)

父组件通过给子组件加 v-bind="$attrs" 属性把数据传递给子孙组件;在子组件中引入useAttrs可获取$attrs里数据

import {useAttrs} from
let attrs = useAttrs()
console.log(attrs)

image.png

子组件会把父组件的class,type,id...,禁止透传这些可设置inheritAttrs: false 未禁止 image.png 禁止操作

import { defineOptions } from 'vue'
defineOptions({
  inheritAttrs: false
})

image.png

传值效果 image.png

父组件

<template>
  <div class="parents">
    <h3>我是父组件</h3>
    <p>爸爸有{{ money1 }}块钱</p>
    <p>儿子给的:{{ lt }}</p>
    <p>孙子给的 {{ food1 }}</p>
    <button @click="giveChildMoney">点击给儿子10元</button>
    <child :money="money" :food="food" :updateMoney="updateMoney" />
  </div>
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue'
import child from '@/components/child.vue'
import emitter from '@/utils/emitter'
let money = ref(100)
let money1 = ref(100)
let childMoney = 0
let lt = ref('')
let food = ref('糖果')
let food1 = ref('')
function giveChildMoney() {
  money.value -= 10
  childMoney += 10
  //  向child组件传值
  emitter.emit('send-money', childMoney)
}
// 接收child组件传的值
emitter.on('send-food', val => {
  lt.value = val as string
})
// 更新父组件数据
function updateMoney(val: string) {
  food1.value = val
}
</script>

<style scoped>
h3 {
  font-weight: bold;
}
button {
  margin-top: 16px;
  padding: 6px 10px;
  border: 1px solid #6b85f8;
  background-color: #6b85f8;
  color: #fff;
  border-radius: 4px;
  cursor: pointer;
}
.parents {
  background: rgb(211, 241, 253);
  box-shadow: 0 0 10px 0 rgba(211, 241, 253, 1);
  border-radius: 8px;
  padding: 20px;
  width: 500px;
}
</style>

子组件

<template>
  <div class="child">
    <h3>我是子组件</h3>
    <p>爸爸给了我{{ money }}块钱让我买好吃的,嘿嘿~</p>
    <p>我买了辣条、棒棒糖、果冻</p>
    <button @click="giveParents()">把辣条给爸爸</button>
    <grandson v-bind="$attrs" />
  </div>
</template>
<script setup lang="ts">
import grandson from './grandson.vue'
import emitter from '@/utils/emitter'
import { ref } from 'vue'
let money = ref(0)
let food = ref('糖果')
// 向其他组件传值
function giveParents() {
  emitter.emit('send-food', '辣条')
}
// 接收其他组件传递值
emitter.on('send-money', val => {
  money.value = val as number
})
</script>

子孙组件

<template>
  <div class="grandson">
    <h3>我是子孙组件</h3>
    <p>爷爷给了我{{ money }}块钱让我买{{ food }}吃,嘿嘿~</p>
    <button @click="updateMoney(food)">给爷爷糖果</button>
  </div>
</template>
<script setup lang="ts" name="Grandson">
defineProps(['money', 'food', 'updateMoney'])
</script>