vue3组件通信常用几种方法

1、props

父传子:通过在父组件中的子组件身上添加属、传递参数;在子组件中通过defineProps接收参数。

<--!父组件-->
<template>
    <div style="background-color: aqua;">
        01_props-<div>
            子给的姓名:{{ childName }}
        </div>
        <child :name="name"></child>
    </div>
</template>
<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue';
const name = ref('孩子爸')
const childName = ref('')
</script>
<--!子组件-->
<template>
    <div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
        子组件
        <div>
            父的名:{{ name }}
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
defineProps(['name'])
</script>

子传父:首先需要在父组件中定义接收的方法,通过子组件defineProps接收父组件方法,调用父组件的方法实现传参。

<--!父组件-->
<template>
    <div style="background-color: aqua;">
        01_props-<div>
            子给的姓名:{{ childName }}
        </div>
        <child :sendName="getName"></child>
    </div>
</template>
<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue';
const childName = ref('')
//定于接收方法
function getName(value: any) {
    childName.value = value
}
</script>
<style lang='scss' scoped></style>
<--!子组件-->
<template>
    <div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
        子组件
        <--! 调用父组件中的方法并传参 -->
        <button @click="sendName(childName)">
            发送姓名
        </button>
    </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const childName = ref('孩子')
defineProps(['sendName'])
</script>

2、custom-event(子传父)通常用于子传父

<!--父组件-->
<template>
    <div style="background-color: aqua;">
        02_custom-event
        <div>父组件</div>
        <div>子组件的值:{{ childName}}</div>
        <!--在父组件中,给子组件绑定自定义事件:-->
        <child @send-child="getChild"></child>
    </div>
</template>

<script setup lang="ts">
import child from './child.vue';
import { ref } from 'vue';
const childName = ref('')
function getChild(value:any){
childName.value = value
}
</script>
<!--子组件-->
1、直接传值
<template>
    <div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
        子组件
        <div>
            子组件:{{ childName }}
        </div>
        <!--子组件中,触发事件: -->
        <buttonc @click="emit('send-child',childName)">传值</buttonc>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const childName = ref('孩子')
//通过defindEmits来接收父组件给子组件绑定的自定义事件
const emit = defineEmits(['send-child'])
</script>
2、经过函数处理后传值
<template>
    <div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
        子组件
        <div>
            子组件:{{ childName }}
        </div>
        <!--子组件中,触发事件: -->
        <button @click="send">传值</button>
    </div>
</template>


<script setup lang="ts">
import { ref } from 'vue';
const childName = ref('孩子')
const emit = defineEmits(['send-child'])
function send(){
childName.value = '孩子2'
emit('send-child',childName.value)
}
</script>

*他们的本质是没有改变的,传值的方式都一样。
*只不过第二种改变了形式,在传参前经过了一些处理,在实际的开发中会更方便

3、mitt 可实现父组件中多个子组件间相互传值

3.1安装

npm install mitt

3.2在src目录下新建utils文件夹,在utils文件夹李新建emitter.ts文件

//引入mitt
import mitt from 'mitt'
//调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt()

//基本用法
  // 绑定事件
  emitter.on('abc',(value)=>{
    console.log('abc事件被触发',value)
  })
  emitter.on('xyz',(value)=>{
    console.log('xyz事件被触发',value)
  })

  setInterval(() => {
    // 触发事件
    emitter.emit('abc',666)
    emitter.emit('xyz',777)
  }, 1000);

  setTimeout(() => {
    // 清理事件
    emitter.all.clear()
  }, 3000);
*/

//对外暴露mitt
export default emitter

3.3使用

<!--父组件-->
<template>
    <div style="background-color: aqua;">
        03_mitt
        <div>父组件</div>
        <div>
            <child></child>
            <child2></child2>
        </div>
    </div>
</template>

<script setup lang="ts">
import child from './child.vue';
import child2 from './child2.vue';
</script>
<!--子组件-01-->
<template>
    <div style="background-color: bisque;margin-left: 10px; width: 80%; height: 80%;">
        子组件1
        <div>
            子组件1{{ childName }}
        </div>
        <!--子组件中,触发事件: -->
        <button @click="send">传值</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import emitter from '@/utils/emitter'
const childName = ref('组件1')
//触发mitt里为组件2绑定的函数,并将自己的值传给组件2
//触发函数时需要传入参数
function send() {
    emitter.emit('send-msg', childName.value)
}
//在mitt里为组件1绑定函数,定义接受参数的函数
emitter.on('send-msg2', (value:any) => {
    console.log("组件1,来自组件2的值:",value);
})
</script>
<!--子组件-02-->
<template>
    <div style="background-color: rgb(225, 134, 23);margin-left: 10px; width: 80%; height: 80%;">
        子组件2
        <div>
            子组件2{{ childName }}
        </div>
        <!--子组件中,触发事件: -->
        <button @click="send">传值</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import emitter from '@/utils/emitter'
const childName = ref('组件2')
//在mitt里为组件2绑定函数,定义接受参数的函数
emitter.on('send-msg', (value:any) => {
    console.log("组件2,来自组件1的值:",value);
})
//触发mitt里为组件1绑定的函数,并将组件的值传给组件1
//触发函数时需要传入参数
function send(){
    emitter.emit('send-msg2',childName.value)
}
</script>

4、v-model实现父子组件通信(自定义input组件)

首先了解一下v-model的本质

<input
     type="text"
     :value="userName"
     @input="userName =(<HTMLInputElement>$event.target).value"
>

如何使用

<!--父组件-->
<template>
    <div style="background-color: aqua;">
        04_v-model(父组件)
        <div>
            <!-- 组件标签上使用v-model指令 -->
            <DemoInput v-model="userName"></DemoInput>
           
            <!-- 组件标签上v-model的本质 -->
            <!-- <DemoInput :modelValue="userName" @update:modelValue="userName = $event" /> -->
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import DemoInput from './demoInput.vue'
const userName = ref("12312312")
</script>
<!--子组件-->
<template>
    <div>
        <!-- v-model的本质是下面这行代码 -->
        <!-- <input
     type="text"
     :value="userName"
     @input="userName =(<HTMLInputElement>$event.target).value"
   > -->
 <!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
      <!--给input元素绑定原生input事件,触发input事件时,进而触发update:model-value事件-->
        <input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)">
    </div>
</template>

<script setup lang="ts">
// 接收props
defineProps(['modelValue'])
// 声明事件
const emit = defineEmits(['update:modelValue'])
</script>

多个v-model(如果value可以更换,那么就可以在组件标签上多次使用v-model

<!--父组件-->
<template>
    <div style="background-color: aqua;">
        04_v-model(父组件)
        <div>
            <!-- 组件标签上使用v-model指令 -->
            <!--<DemoInput v-model="userName"></DemoInput>-->
            <!-- 修改modelvaue -->
            <DemoInput v-model:username="userName" v-model:password="passWord"></DemoInput>
           
            <!-- 组件标签上v-model的本质 -->
            <!-- <DemoInput :modelValue="userName" @update:modelValue="userName = $event" /> -->
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import DemoInput from './demoInput.vue'
const userName = ref("12312312")
</script>
<!--子组件-->
<template>
    <div>
        <input type="text" :value="username" @input="emit('update:username', (<HTMLInputElement>$event.target).value)">

        <input type="text" :value="password" @input="emit('update:password', (<HTMLInputElement>$event.target).value)">
    </div>
</template>


<script setup lang="ts">
// 接收props
defineProps(['username', 'password'])
// 声明事件
const emit = defineEmits(['update:username', 'update:password'])
</script>

5、attrs

5.1. 概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。

5.2. 具体说明:$attrs是一个对象,包含所有父组件传入的标签属性。

注意:$attrs会自动排除props中声明的属性(可以认为声明过的 props 被子组件自己“消费”了)

template>
    <div style="background-color: blanchedalmond;">
        05_$attrs
        <h4>a:{{ a }}</h4>
        <h4>b:{{ b }}</h4>
        <h4>c:{{ c }}</h4>
        <child :a="a" :b="b" :c="c" :updateA="updatedA"></child>
    </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import child from "./child.vue";
const a = ref(1)
const b = ref(2)
const c = ref(3)
function updatedA (value:number) {
    a.value +=value
}
</script>
<!--子组件-->
<template>
    <div style="background-color: rgb(238, 131, 1);margin: 10px;">
        子组件
        <grandChild v-bind="$attrs"></grandChild>
    </div>
</template>

<script setup lang="ts">
import grandChild from './grandChild.vue';
</script>
<template>
    <div style="background-color: aqua;margin: 10px;">
        孙组件
        <h4>a:{{ a }}</h4>
        <h4>b:{{ b }}</h4>
        <h4>c:{{ c }}</h4>
        <button @click="updateA(1)">点我给爷爷a+1</button>
    </div>
</template>


<script setup lang="ts">
defineProps(['a','b','c','updateA'])
</script>

6、 r e f s 、 refs、 refsparent

  • $refs用于 :父→子。
  • $parent用于:子→父。

想要组件拿到数据,必须要使用defineExpose将数据交出

原理如下:

属性说明
$refs值为对象,包含所有被ref属性标识的DOM元素或组件实例。
$parent值为对象,当前组件的父组件实例对象。
<!--父组件-->
<template>
    <div style="background-color: blanchedalmond;">
        06_$ref-$parent
        <h2>父组件</h2>
        <h2>房子:{{ home }}</h2>
        <button @click="addCar">给c1再买一辆车</button>
        <button @click="addPhone">给c2再买一部手机</button>
        <button @click="getALL($refs)">获取所有的子组件的实例对象</button>
        <div style="display: flex;">
            <child1 ref="c1" />
            <child2 ref="c2"></child2>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import child1 from './child1.vue';
import child2 from './child2.vue';
const home = ref(4)

const c1 = ref()
const c2 = ref()
//方法
function addCar() {
    c1.value.car += 1
    console.log(c1.value);
}
function addPhone() {
    c2.value.phone += 1
}
function getALL(refs:{[key:string]:any}) {
    for (let key in refs) {
        refs[key].money += 100
        // console.log(key);
    }
    // console.log(ref);
}
//把数据交出去
defineExpose({home})
</script>
<!--组件1-->
<template>
    <div style="background-color: aquamarine;margin-right: 20px;">
        子组件1
        <h4>车:{{ car }}</h4>
        <h4>资产:{{money}}</h4>
        <button @click="minHome($parent)">拿走父亲一套房</button>
    </div>
</template>


<script setup lang="ts">
import { ref } from "vue";


const car = ref(2)
const money = ref(100)
//子组件管理父组件里的数据
function minHome(parent:object){
    console.log(parent);
    parent.home -=1
}
//把数据交出去
defineExpose({car,money})
</script>
<!--子组件2-->
<template>
    <div style="background-color: aquamarine;">
        子组件2
        <h4>手机:{{phone}}</h4>
        <h4>资产:{{money}}</h4>
        <button @click="addHome($parent)">给父亲买一套房</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const phone = ref(3)
const money = ref(100)
//子组件管理父组件里的数据
function addHome(parent:object){
    console.log(parent);
    parent.home +=1  
}
//把数据交出去
defineExpose({phone,money})
</script>

7、provide-inject

注意:子组件中不用编写任何东西,是不受到任何打扰的

  • 在祖先组件中通过provide配置向后代组件提供数据
  • 在后代组件中通过inject配置来声明接收数据
<!--父组件:父组件中,使用`provide`提供数据-->
<template>
    <div style="background-color: blanchedalmond;">
        07_provide-inject
        <h3>父组件</h3>
        <h4>资产{{ money}}w</h4>
        <h4>汽车{{ car.brand }},价值{{car.price}}w</h4>
        <child></child>
    </div>
</template>

<script setup lang="ts">
import child from "./child.vue";
import { reactive, ref , provide} from "vue";
const money = ref(100)
const car  =reactive({
    brand:'奥迪',
    price:100
})
function addMoney(){
money.value += 100
}
//向后代提供数据
provide('Money',{money,addMoney})
provide('Car',car)
</script>
<!--孙组件:孙组件中使用`inject`配置项接受数据。-->
<template>
    <div style="background-color: aqua;margin: 10px;">
        孙组件
        <h4>资产{{ money}}w</h4>
        <h4>汽车{{ car.brand }},价值{{car.price}}w</h4>
        <button @click="addMoney(100)">给爷爷钱</button>
    </div>
</template>

<script setup lang="ts">
import { inject } from 'vue';
let {money,addMoney} = inject('Money',{money:0,addMoney:(x:number)=>{}})
//{brand:'未知',price:0}帮助推断
let car = inject('Car',{brand:'未知',price:0})
</script>

8、pinia组件通信

链接: 可以看我之前发布的pinia教程里面有详细介绍

9、【slot】插槽

9.1默认插槽

<!--父组件-->
<template>
    <div style="background-color: aquamarine;">
        09_slot
        <h3>父组件</h3>
        <div style="display: flex;justify-content: space-evenly;width: 100%;height: 80%;">
            <Category title="热门游戏">
                <div v-for="game in games" :key="game.id" style="background-color: yellowgreen;">
                    {{ game.name }}
                </div>
            </Category>
            <Category   title="美食分享">
                <img style="width: 200px;" :src="imgUrl" alt="" srcset="">
            </Category>
            <Category title="影视推荐">
                <video controls :src="videoUrl"></video>
            </Category>
        </div>
    </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import Category from './Category.vue';
let games = reactive([
    { id: 'asgdytsa01', name: '英雄联盟' },
    { id: 'asgdytsa02', name: '王者荣耀' },
    { id: 'asgdytsa03', name: '红色警戒' },
    { id: 'asgdytsa04', name: '斗罗大陆' }
])
let imgUrl =ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://clips.vorwaerts-gmbh.de/bigbuckbunny.mp4')
</script>
<!--子组件-->
<template>
    <div style="background-color: blanchedalmond;">
        <!-- <h4>子组件</h4> -->
        <h2>{{ title }}</h2>
<!-- 默认插槽 -->
        <slot></slot>
    </div>
</template>

<script setup lang="ts">
defineProps(['title'])
</script>

9.2具有名字的插槽

通过在父组件中引入的子组件身上添加 <template v-slot:s1>或者<template #s2>来定义父组件中的元素具体放在子组件中的那个位置

<!--父组件-->
<template>
    <div style="background-color: aquamarine;">
        09_slot
        <h3>父组件</h3>
        <div style="display: flex;justify-content: space-evenly;width: 100%;height: 80%;">
            <Category title="热门游戏">
                <template v-slot:s1>
                    <div v-for="game in games" :key="game.id" style="background-color: yellowgreen;">
                        {{ game.name }}
                    </div>
                </template>
<!--一个组件中可以放置多个<template>标签-->
                <template #s2>
                    <div>more</div>
                </template>
            </Category>
            <Category title="美食分享">
                    <img style="width: 200px;" :src="imgUrl" alt="" srcset="">
            </Category>
            <Category title="影视推荐">
                <video controls :src="videoUrl"></video>
            </Category>
        </div>
    </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import Category from './Category.vue';
let games = reactive([
    { id: 'asgdytsa01', name: '英雄联盟' },
    { id: 'asgdytsa02', name: '王者荣耀' },
    { id: 'asgdytsa03', name: '红色警戒' },
    { id: 'asgdytsa04', name: '斗罗大陆' }
])
let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://clips.vorwaerts-gmbh.de/bigbuckbunny.mp4')
</script>
<!--子组件-->
<template>
    <div style="background-color: blanchedalmond;">
        <!-- <h4>子组件</h4> -->
        <h2>{{ title }}</h2>
        <slot name="s1"></slot>
        <slot name="s2"></slot>
    </div>
</template>

<script setup lang="ts">
defineProps(['title'])
</script>

9.3作用域插槽

所有的数据和行为由子组件决定,但是数据呈现的方式由父组件决定;也可以拥有具有名字的插槽

<!--父组件-->
<template>
    <div style="background-color: aquamarine;">
        09_slot
        <h3>父组件</h3>
        <div style="display: flex;justify-content: space-evenly;width: 100%;height: 80%;">
            <game title="游戏列表">
                <template v-slot:GAME="params">
                    <div v-for="g in params.games" :key="g.id">
                    {{ g.name }}
                    </div>
                </template>
				<!-- 默认插槽不能带有具名 -->
                <!-- <template #default="params">
                    <div v-for="g in params.games" :key="g.id">
                    {{ g.name }}
                    </div>
                </template> -->
            </game>
        </div>
    </div>
</template>

<script setup lang="ts">
import game from './game.vue';
</script>
<!--子组件-->
<template>
    <div class="category">
        <h2>{{ title }}</h2>
        <slot name="GAME" :games="games"></slot>
    </div>
</template>

<script setup lang="ts" name="Category">
import { reactive } from 'vue'
defineProps(['title'])
let games = reactive([
    { id: 'asgdytsa01', name: '英雄联盟' },
    { id: 'asgdytsa02', name: '王者荣耀' },
    { id: 'asgdytsa03', name: '红色警戒' },
    { id: 'asgdytsa04', name: '斗罗大陆' }
])
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值