【Vue3】组件通信

目录

前言

一、props

二、自定义事件

三、mitt

四、v-model

五、$attrs

六、$refs、$parent

七、provide、inject

八、pinia

1、搭建pinia环境

2、存储数据

3、组件中使用数据

4、修改数据

5、storeToRefs

6、$subscribe

7、组合式写法

九、slot

1、默认插槽

2、具名插槽

3、作用域插槽


前言

常见的组件关系有:父子组件、祖孙组件、兄弟组件以及任意组件,不同组件之间数据传递有不同的方法。

父传子:

props、v-model、$refs、默认插槽、具名插槽

子传父:

props、自定义事件、v-model、$parent、作用域插槽

祖传孙、孙传祖:

$arrts、provide、inject

兄弟组件、任意组件之间传值:

mitt、pinia

一、props

props通常用于父子组件之间传值,父传子属性是非函数,子传父属性是函数。

父组件:

<template>
  <div class="app">
    <h1>父组件</h1>
    <div>接收到的子组件数据:{{ getChildData }}</div>
    <Child :data="parentMsg" :updataChildData="toUpdataChildData"></Child>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './components/child.vue'

const parentMsg = ref('你好')
const getChildData = ref(null)

const toUpdataChildData = (childData) => {
  getChildData.value = childData
}
</script>

<style lang="scss" scoped>
.app{
  padding: 20px;
  background-color: skyblue;
  border-radius: 10px;
}
</style>

子组件child.vue

<template>
  <div class="container">
    <h1>子组件</h1>
    <div>接收到的父组件数据:{{ data }}</div>
    <button @click="updataChildData(childData)">传值给父组件</button>
  </div>
</template>

<script setup>
import { ref } from "vue"

const props = defineProps(['data', 'updataChildData'])
const childData = ref(1000)
</script>

<style lang="scss" scoped>
.container{
  background-color: pink;
  padding: 20px;
  border-radius: 10px;
}
</style>

 

二、自定义事件

自定义事件通常用于子组件给父组件传值,在子组件标签中绑定自定义事件,然后在子组件内通过emit触发自定义事件并传递数据。

父组件:

<template>
  <div class="app">
    <h1>父组件</h1>
    <div>子组件传递过来的数据:{{ data }}</div>
    <Child @send-data="giveChildData"></Child>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './components/child.vue'

const data = ref(null)
const giveChildData = (childData) => {
  data.value = childData
}
</script>

子组件:

<template>
  <div class="container">
    <h1>子组件</h1>
    <div>子组件的数据:{{ msg }}</div>
    <button @click="emit('send-data', msg)">传递数据</button>
  </div>
</template>

<script setup>
import { ref } from "vue"

const msg = ref('你好')
const emit = defineEmits(['send-data'])
</script>

 

三、mitt

mitt相当于vue2中的事件总线,利用它可以实现任意组件之间的数据传递。

1、安装

npm i mitt

2、创建

src/utils/emitter.js

// 引入mitt 
import mitt from "mitt"
// 创建emitter
const emitter = mitt()
export default emitter

3、使用

接收数据的组件:

import emitter from "@/utils/emitter"
import { onUnmounted } from "vue"

// 绑定事件
emitter.on('send-data',(value)=>{
  console.log('获取到的数据',value)
})

onUnmounted(()=>{
  // 解绑事件
  emitter.off('send-data')
})

提供数据的组件:

import emitter from "@/utils/emitter"

const sendData = () => {
  // 触发事件
  emitter.emit('send-data',data)
}

四、v-model

v-model可以实现父子组件之间的相互通信。

🌱 input表单元素上v-model的本质:

<!-- 使用v-model指令 -->
<input type="text" v-model="userName">

<!-- v-model本质 -->
<input 
  type="text" 
  :value="userName" 
  @input="userName = "$event.target.value"
>

🌱 组件标签上使用v-model指令,它的本质是相当于给子组件传递一个props(modelValue)并且绑定一个自定义事件update:modelValue,从而实现父子组件之间的数据同步。只使用一个v-model时,不需要指定变量名,默认是modelValue。

<!-- 组件标签上使用v-model指令 -->
<CustomInput v-model="userName"/>

<!-- 组件标签上v-model的本质 -->
<CustomInput :modelValue="userName" @update:model-value="userName = $event"/>

CustomInput.vue

<template>
  <div class="box">
    <input 
       type="text" 
       :value="modelValue" 
       @input="emit('update:model-value',$event.target.value)"
    >
  </div>
</template>

<script setup name="AtguiguInput">
  // 接收props
  defineProps(['modelValue'])
  // 声明事件
  const emit = defineEmits(['update:model-value'])
</script>

🌱 v-model还可以同时绑定多个值:

 父组件:

<template>
  <div class="app">
    <h1>父组件</h1>
    <div>a:{{ a1 }},b:{{ b1 }},c:{{ c1 }}</div>
    <button @click="updateData">更新数据</button>
    <Child 
      v-model:a="a1" 
      v-model:b="b1" 
      v-model:c="c1"
    ></Child>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './components/child.vue'

const a1 = ref(111)
const b1 = ref(222)
const c1 = ref(333)

const updateData = () => {
  a1.value += 10
  b1.value += 10
  c1.value += 10
}
</script>

子组件child:

<template>
  <div class="container">
    <h1>子组件</h1>
    <div>a:{{ a }},b:{{ b }},c:{{ c }}</div>
    <button @click="updateData">更新数据</button>
  </div>
</template>

<script setup>
const props = defineProps(['a', 'b', 'c'])
const emit = defineEmits(['update:a', 'update:b', 'update:c'])

const updateData = () => {
  emit('update:a', 111000)
  emit('update:b', 222000)
  emit('update:c', 333000)
}
</script>

 

五、$attrs

$attrs可以实现祖组件向孙组件传值,每个子组件都可以通过$attrs将没有被props接收的数据传递给它的子组件。

父组件:

<template>
  <div class="father">
    <h3>父组件</h3>
	<Child :a="a" :b="b" :c="c" />
  </div>
</template>

<script setup name="Father">
	import Child from './Child.vue'
	import { ref } from "vue"
	let a = ref(1)
	let b = ref(2)
	let c = ref(3)
</script>

子组件:

<template>
	<div class="child">
		<h3>子组件</h3>
		<GrandChild v-bind="$attrs"/>
	</div>
</template>

<script setup 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>
	</div>
</template>

<script setup name="GrandChild">
	defineProps(['a','b','c'])
</script>

👉ps:$attrs可以多层传递。 

六、$refs、$parent

$refs用于父组件给子组件传值,通过$refs可以获取到子组件中向外暴露的所有方法和数据,$parent用于子组件给父组件传值,通过$parent可以获取到父组件中向外暴露的所有方法和数据。

使用ref需要在子组件标签上通过ref属性给它定义一个唯一标识,然后通过ref函数创建对子组件的引用,通过这个引用可以获取到子组件中向外暴露的所有方法和数据。

$refs:包含所有被ref属性标识的DOM元素或组件实例。

$parent:当前组件的父组件实例对象。

父组件:

<template>
  <div class="app">
    <h1>父组件</h1>
    <div>父组件number值:{{ number }}</div>
    <div>子组件1的数据:{{ msg1 }}</div>
    <div>子组件2的数据:{{ msg2 }}</div>
    <button @click="updateData">给子组件传递数据</button>
    <button @click="getAllChild($refs)">获取所有被ref属性标识的组件</button>
    <!-- 通过ref标识子组件 -->
    <Child1 ref="child1"></Child1>
    <Child2 ref="child2"></Child2>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'
import Child1 from './components/child1.vue'
import Child2 from './components/child2.vue'

const msg1 = ref('')
const msg2 = ref('')
const number = ref(10000)

// 创建对子组件的引用
const child1 = ref()
const child2 = ref()
// 给子组件传递数据
const updateData = () => {
  child1.value.childData += '6666'
  child2.value.childData += '7777'
}
// 获取子组件数据 
onMounted(() => {
  msg1.value = child1.value.childData
  msg2.value = child2.value.childData
})

const getAllChild = (refs) => {
  console.log(refs);
  for (let key in refs){
    refs[key].childData += '~~~~'
  }
}

// 需要通过defineExpose暴露出去,子组件才能获取到
defineExpose({
  number
})
</script>

子组件child1:

<template>
  <div class="container">
    <h1>子组件1</h1>
    <div>子组件的数据:{{ childData }}</div>
    <button @click="subNum($parent)">父组件的number值减100</button>
  </div>
</template>

<script setup>
import { ref } from "vue"

const childData = ref('你好')

const subNum = (parent) => {
  parent.number -= 100
}

// 需要通过defineExpose暴露出去,父组件才能获取到
defineExpose({
  childData
})
</script>

子组件child2:

<template>
  <div class="container">
    <h1>子组件2</h1>
    <div>子组件的数据:{{ childData }}</div>
  </div>
</template>

<script setup>
import { ref } from "vue"

const childData = ref('hello')

// 需要通过defineExpose暴露出去,父组件才能获取到
defineExpose({
  childData
})
</script>

 

七、provide、inject

provide和inject可以实现祖孙组件之间的通信。祖先组件通过provide向其后代组件传递数据,后代组件使用inject获取数据。

父传子,传递变量;子传父,传递函数。

父组件:

<template>
  <div class="app">
    <h1>父组件</h1>
    <div>父组件数据:姓名{{ data.name }},年龄:{{ data.age }}</div>
    <button @click="updateData">更新数据</button>
    <Child></Child>
  </div>
</template>

<script setup>
import { provide, ref } from 'vue'
import Child from './components/child.vue'

const data = ref({
  name: 'zhangsan',
  age: 19
})

const updateAge = (sendAge) => {
  data.value.age = sendAge
}

provide('parentData', data)
// 传递函数给后代组件,可以让后代组件向父组件传值
provide('updateAge', updateAge)

const updateData = () => {
  data.value.age += 1
  data.value.name += '~'
}
</script>

子组件child:

<template>
  <div class="container">
    <h1>子组件1</h1>
    <div>获取到的父组件数据:姓名{{ info.name }},年龄:{{ info.age }}</div>
    <GrandChild />
  </div>
</template>

<script setup>
import GrandChild from './grandChild.vue'
import { inject, ref } from "vue"
// 第一个参数是key值,第二个参数是默认值 
const info = inject('parentData', {
  name: '未知',
  age: '未知'
})
</script>

孙组件grandChild:

<template>
  <div class="container1">
    <h1>孙组件</h1>
    <div>获取到的父组件数据:姓名{{ info.name }},年龄:{{ info.age }}</div>
    <button @click="updateAge(info.age+10)">年龄+10</button>
  </div>
</template>

<script setup>
import { inject, ref } from "vue"

const info = inject('parentData', {
  name: '未知',
  age: '未知'
})
const updateAge = inject('updateAge')
</script>

 

八、pinia

相当于vue2中的vuex,pinia也是vue.js状态管理库。

1、搭建pinia环境

下载

npm install pinia

创建

src/main.js

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

2、存储数据

它有三个概念:store、getters、actions,相当于组件中的data、computed和methods。

src/store/count.js

// 引入defineStore用于创建store
import {defineStore} from 'pinia'

// 定义并暴露一个store
export const useCountStore = defineStore('count',{
  // 动作
  actions:{
    increment(value) {
      this.sum += value
    }
  },
  // 状态
  state(){
    return {
      sum:6,
      a:1,
      b:2,
      c:3
    }
  },
  // 计算
  getters:{
    bigSum:(state) => state.sum *10,
  }
})

actions中可以通过this来获取到state中的数据,getters用于处理state中的数据。

3、组件中使用数据

<template>
  <h2>state中的数据:{{ countStore.sum }}</h2>
  <h2>getters处理的数据:{{ countStore.bigSum }}</h2>
  <button @click="add">点击加三</button>
</template>

<script setup name="Count">
  // 引入对应的useXxxxxStore	
  import {useCountStore} from '@/store/count'
  
  // 调用useXxxxxStore得到对应的store
  const countStore = useCountStore()

  const add = () => {
    //调用actions中的方法
    countStore.increment(3)
  }
</script>

4、修改数据

(1)直接修改

countStore.sum = 666

(2)批量修改

countStore.$patch({
  sum:999,
  a:11,
  b:22,
  c:33
})

(3)通过actions修改,然后在组件中调用actions中的方法

5、storeToRefs

使用storeToRefs可以将store中的数据转换成ref对象。

注意:pinia中的storeToRefs只会对数据本身转换,而vue中的toRef会转换store中的数据。

我们通过解构拿到的数据不是响应式的,所以需要使用storeToRefs将它们变成响应式的。

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

6、$subscribe

使用$subscribe方法可以侦听store中的数据变化

countStore.$subscribe((mutate,state)=>{
  console.log(mutate, state);
})

7、组合式写法

import {defineStore} from 'pinia'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talk',()=>{
  // talkList就是state
  const talkList = reactive({})

  // getATalk函数相当于action
  function getATalk(){}
  
  return {
    talkList,
    getATalk
  }
})

九、slot

1、默认插槽

组件标签之间的内容会被放到默认插槽<slot></slot>当中,如果没有默认插槽,则这些内容不会显示出来。

父组件中:
        <Child :title="title">
          <ul>
            <li v-for="g in games" :key="g.id">{{ g.name }}</li>
          </ul>
        </Child>
Child子组件中:
        <template>
          <div class="item">
            <h3>{{ title }}</h3>
            <!-- 默认插槽 -->
            <slot></slot>
          </div>
        </template>

2、具名插槽

父组件中:
        <Child :title="title">
          <template v-slot:s1>
            <ul>
              <li v-for="g in games" :key="g.id">{{ g.name }}</li>
            </ul>
          </template>
          <template #s2>
            <div>你好</div>
          </template>
        </Child>
Child子组件中:
        <template>
          <div class="item">
            <h3>{{ title }}</h3>
            <slot name="s1"></slot>
            <slot name="s2"></slot>
          </div>
        </template>

使用时,有两种写法:

(1)v-slot:name

(2)#name

内容会被放到对应名字的插槽中

3、作用域插槽

数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定,相当于子组件给父组件传值。

父组件中:
      <Game v-slot="params">
        <ul>
          <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
        </ul>
      </Game>

子组件中:
      <template>
        <div class="category">
          <h2>今日游戏榜单</h2>
          <slot :games="games" a="哈哈"></slot>
        </div>
      </template>

      <script setup lang="ts" name="Category">
        import {reactive} from 'vue'
        let games = reactive([
          {id:'asgdytsa01',name:'英雄联盟'},
          {id:'asgdytsa02',name:'王者荣耀'},
          {id:'asgdytsa03',name:'红色警戒'},
          {id:'asgdytsa04',name:'斗罗大陆'}
        ])
      </script>
  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值