vue3组件通信

props【主要用于父给子传数据】

props 可以实现父给子传递数据。
如果要用props实现子给父传递数据,那么父组件要先传递一个函数给子组件(sendToy),子组件再以传参的形式将数据传递给父组件。

Father.vue 父组件

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4>汽车:{{ car }}</h4>
        <h4 v-show="toy">子传递过来的玩具:{{ toy }}</h4>
        // 将sendToy函数传递给子组件
        <Child :car="car" :sendToy="getToy"/>
    </div>
</template>

<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref } from 'vue'
    let car = ref('奔驰')
    let toy = ref('')

    // 接收子给父传递的数据
    function getToy(value: string) {
        toy.value = value
    }
</script>

<style scoped>
    .father {
        background-color: rgb(165, 164, 164);
        padding: 20px;
        border-radius: 10px;
    }
</style>

Child.vue 子组件

<template>
    <div class="child">
        <h3>子组件</h3>
        <h4>玩具:{{ toy }}</h4>
        <h4>父亲传递过来的车:{{ car }}</h4>
        <button @click="sendToy(toy)">把玩具传给父亲</button>
    </div>
</template>

<script setup lang="ts" name="Child">
    import { ref } from 'vue'
    let toy = ref('小熊')

    // 声明接收props
    defineProps(['car', 'sendToy'])
</script>

<style scoped>
    .child {
        background-color: skyblue;
        padding: 10px;
        box-shadow: 0 0 10px black;
        border-radius: 10px;
    }
</style>

自定义事件【主要用于子给父传递数据】

首先给子组件绑定自定义事件(send-toy),在子组件中声明事件,当子组件触发该自定义事件时,就会调用回调函数(saveToy)。
defineEmits用于在setup中注册自定义事件
父组件

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4 v-show="toy">子传递过来的玩具:{{ toy }}</h4>
        <!-- 给子组件绑定自定义事件 send-toy -->
        <Child @send-toy="saveToy" />
    </div>
</template>

<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref } from 'vue'
    let toy = ref('')

    // 用于保存传递过来的玩具
    function saveToy(value: string) {
        toy.value = value
    }
</script>

子组件

<template>
  <div class="child">
    <h3>子组件</h3>
    <h4>玩具:{{ toy }}</h4>
    <button @click="emit('send-toy', toy)">点我传递数据</button>
  </div>
</template>

<script setup lang="ts" name="Child">
    import { ref } from "vue"
    let toy = ref('小熊')

    // 声明事件
    const emit = defineEmits(['send-toy'])
</script>

mitt【用于任意组件间通信】

首先下载mitt:npm i mitt
创建utils文件夹,建立emitter.ts文件

import mitt from 'mitt'

// 调用 mitt得到 emitter,emitter能绑定事件,触发事件
const emitter = mitt()

export default emitter

如果子组件1要给子组件2传递数据【子组件1====>子组件2】,
就给子组件2绑定事件【emitter.on(‘事件名’)】,
子组件1触发该事件并传递数据【emitter.emit(‘事件名’, 要传递的数据)】
子组件1

<template>
    <div class="child1">
        <h3>子组件1</h3>
        <h4>玩具:{{ toy }}</h4>
        <button @click="emitter.emit('send-toy', toy)">
            点我传递数据
        </button>
    </div>
</template>

<script setup lang="ts" name="Child1">
    import { ref } from 'vue'
    import emitter from '@/utils/emitter'
    let toy = ref('小熊')
</script>

子组件2

<template>
    <div class="child2">
        <h3>子组件2</h3>
        <h4>电脑:{{ computer }}</h4>
        <h4 v-show="toy">得到了哥哥传递过来的玩具:{{ toy }}</h4>
    </div>
</template>

<script setup lang="ts" name="Child2">
    import { ref, onUnmounted } from 'vue'
    import emitter from '@/utils/emitter'
    let computer = ref('联想')
    let toy = ref('')

    // 给emitter绑定事件send-toy
    emitter.on('send-toy', (value:any) => {
        toy.value = value
    })

    // 在组件卸载时解绑send-toy事件
    onUnmounted(() => {
        emitter.off('send-toy')
    })
</script>

v-model【父传子,子传父】

v-model既可以用在html标签上,用于双向数据绑定,也可以用在组件标签上。
父组件

<template>
    <div class="father">
        <h3>父组件</h3>

        <!-- v-model用在html标签上 -->
        <!-- <input type="text" v-model="username" /> -->
        <!-- <input type="text" :value="username" @input="username = <HTMLInputElement>($event.target).value" /> -->

        <!-- v-model用在组件标签上 -->
        <AtguiguInput v-model="username"/>
        <!-- <AtguiguInput
            :modelValue="username"
            @update:modelValue="username = $event" /> -->
    </div>
</template>

<script setup lang="ts" name="Father">
    import { ref } from 'vue'
    import AtguiguInput from './AtguiguInput.vue'
    // 数据
    let username = ref('zhansan')
</script>

子组件

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

<script setup lang="ts" name="AtguiguInput">
    defineProps(['modelValue'])
    const emit = defineEmits(['update:modelValue'])
</script>

修改modelValue
父组件

<!-- 修改 modelValue-->
<AtguiguInput v-model:qwe="username" v-model:mima="password"/>

子组件

<template>
    <input
        type="text"
        :value="qwe"
        @input="emit('update:qwe', $event.target.value)" />
        <br>
    <input
        type="text"
        :value="mima"
        @input="emit('update:mima', $event.target.value)" />
</template>

<script setup lang="ts" name="AtguiguInput">
    defineProps(['qwe', 'mima'])
    const emit = defineEmits(['update:qwe', 'update:mima'])
</script>
对于原生事件,$event 就是事件对象,可以 $event.target
对于自定义事件, $event 就是触发事件时,所传递的数据,不能 $event.target

$attrs【用于祖传孙】

父组件将数据传给子组件,子组件利用 $attrs 将数据全部传给孙组件(<GrandChild v-bind=" $attrs"/ >),孙组件利用defineProps接收传递过来的数据。【需要中间人】
父组件

<template>
  <div class="father">
    <h3>父组件</h3>
		<h4>a:{{a}}</h4>
		<h4>b:{{b}}</h4>
		<h4>c:{{c}}</h4>
		<h4>d:{{d}}</h4>
		<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100, y:200}" :updateA="updateA"/>
  </div>
</template>

<script setup lang="ts" name="Father">
	import Child from './Child.vue'
	import {ref} from 'vue'

	let a = ref(1)
	let b = ref(2)
	let c = ref(3)
    let d = ref(4)

    function updateA(value:number) {
        a.value += value
    }
</script>

子组件

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

<script setup lang="ts" 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>
        <h4>d:{{ d }}</h4>
        <h4>x:{{ x }}</h4>
        <h4>y:{{ y }}</h4>
        <button @click="updateA(5)">点我更新爷爷传过来的a</button>
    </div>
</template>

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

$refs【父传子】 $parent【子传父】

$refs 包含父组件所有的孩子
$parent 可以得到子组件的父亲
使用 $refs 和 $parent 都需要子组件或父组件向外部提供数据【defineExpose】
父组件

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4>房产:{{ house }}</h4>
        <button @click="changeToy">修改Child1的玩具</button>
        <button @click="changeComputer">修改Child2的电脑</button>
        <button @click="getAllChild($refs)">让所有孩子的书变多</button>
        <Child1 ref="c1" />
        <Child2 ref="c2" />
    </div>
</template>

<script setup lang="ts" name="Father">
    import Child1 from './Child1.vue'
    import Child2 from './Child2.vue'
    import { ref} from 'vue'

    let c1 = ref()
    let c2 = ref()

    let house = ref(4)

    function changeToy() {
        c1.value.toy = '小熊'
    }
    function changeComputer() {
        c2.value.computer = '华为'
    }
    function getAllChild(refs:any) {
        for (let key in refs) {
            // console.log(refs[key])
            refs[key].book += 3
        }
    }
    // 向外部提供数据
    defineExpose({house})
</script>

子组件1

<template>
    <div class="child1">
        <h3>子组件1</h3>
        <h4>玩具:{{ toy }}</h4>
        <h4>书籍:{{ book }} 本</h4>
        <button @click="minusHouse($parent)">干掉父亲的一套房产</button>
    </div>
</template>

<script setup lang="ts" name="Child1">
    import { ref } from 'vue'
    // 数据
    let toy = ref('奥特曼')
    let book = ref(3)

    function minusHouse(parent: any) {
        parent.house -= 1
    }

    // 把数据交给外部
    defineExpose({ toy, book })
</script>

子组件2

<template>
    <div class="child2">
        <h3>子组件2</h3>
        <h4>电脑:{{ computer }}</h4>
        <h4>书籍:{{ book }} 本</h4>
    </div>
</template>

<script setup lang="ts" name="Child2">
    import { ref } from 'vue'
    // 数据
    let computer = ref('联想')
    let book = ref(6)
    // 把数据交给外部
    defineExpose({ computer, book })
</script>

provide-inject【祖孙直接通信】

父组件利用provide直接向后代传递数据(所有后代都能使用),子组件或者孙组件利用inject接收数据。(不需要中间人)
父组件

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4>金钱:{{ money }}</h4>
        <h4>车子:{{ car.brand }},价格:{{ car.price }}</h4>
        <Child />
    </div>
</template>

<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref, reactive, provide } from 'vue'

    let money = ref(100)
    let car = reactive({
        brand: '奔驰',
        price: 200,
    })

    function updateMoney(value: number) {
        money.value -= value
    }

    // provide 第一个参数:参数的名字;第二个参数:参数的值
    // 向后代提供数据(所有后代都可以使用)
    provide('moneyContext', {money, updateMoney})
    provide('car', car)
</script>

孙组件

<template>
    <div class="grand-child">
        <h3>我是孙组件</h3>
        <h4>得到的钱:{{ money }}</h4>
        <h4>得到的车:{{ car.brand }},价值{{ car.price }}</h4>
        <button @click="updateMoney(5)">花爷爷的钱</button>
    </div>
</template>

<script setup lang="ts" name="GrandChild">
    import { inject } from 'vue'
    let {money, updateMoney} = inject('moneyContext', {money:0, updateMoney:(x:number)=>{}})
    let car = inject('car', { brand: '未知', price: 0 })
</script>

pinia

插槽

默认插槽

默认插槽是将父组件的结构和数据插入子组件中,默认插槽只有一个插入位置。父组件决定结构和数据
父组件

<template>
    <div class="father">
        <h3>父组件</h3>
        <div class="content">
            <Category title="热门游戏列表">
                <ul>
                    <li v-for="game in games" :key="game.id">
                        {{ game.name }}
                    </li>
                </ul>
            </Category>
            <Category title="今日美食城市">
                <img :src="imgUrl" />
            </Category>
            <Category title="今日影视推荐">
                <video :src="videoUrl"></video>
            </Category>
        </div>
    </div>
</template>

<script setup lang="ts" name="Father">
    import Category from './Category.vue'
    import { ref, reactive } from 'vue'
    let games = reactive([
        { id: 'asgytdfatso1', name: '英雄联盟' },
        { id: 'asgytdfatse2', name: '王者农药' },
        { id: 'asgytdfatso3', name: '红色警戒' },
        { id: 'asgytdfatse4', name: '斗罗大陆' },
    ])
    let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
    let videoUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')
</script>

子组件

<template>
    <div class="category">
        <h2>{{ title }}</h2>
        <slot>默认内容</slot>
    </div>
</template>

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

具名插槽

具名插槽简单地说就是具有名字的插槽,只是默认插槽只有一个插入位置,具名插槽可以有多个插入位置,根据名字来识别对应的插槽。父组件决定结构和数据。

<template>
    <div class="father">
        <h3>父组件</h3>
        <div class="content">
            <Category>
                <template v-slot:s2>
                    <ul>
                        <li v-for="game in games" :key="game.id">
                            {{ game.name }}
                        </li>
                    </ul>
                </template>
                <template v-slot:s1>
                    <h2>热门游戏列表</h2>
                </template>
            </Category>
            <Category>
                <template v-slot:s2>
                    <img :src="imgUrl" />
                </template>
                <template v-slot:s1>
                    <h2>今日美食城市</h2>
                </template>
            </Category>
            <Category>
                <template #s2>
                    <video :src="videoUrl"></video>
                </template>
                <template #s1>
                    <h2>今日影视推荐</h2>
                </template>
            </Category>
        </div>
    </div>
</template>

<script setup lang="ts" name="Father">
    import Category from './Category.vue'
    import { ref, reactive } from 'vue'
    let games = reactive([
        { id: 'asgytdfatso1', name: '英雄联盟' },
        { id: 'asgytdfatse2', name: '王者农药' },
        { id: 'asgytdfatso3', name: '红色警戒' },
        { id: 'asgytdfatse4', name: '斗罗大陆' },
    ])
    let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
    let videoUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')
</script>
<template>
    <div class="category">
        <slot name="s1">默认内容</slot>
        <slot name="s2">默认内容</slot>
    </div>
</template>

<script setup lang="ts" name="Category">
</script>

作用域插槽

当数据在子组件中,但是插槽中渲染数据的节点由父组件决定,这种情况下,就需要使用作用域插槽。
可以叫做带数据的插槽,父组件在使用的时候可以替换slot插槽中的显示页面结构,但展示的数据还是来源于子组件。
子组件决定数据,父组件决定结构。
父组件

<template>
    <div class="father">
        <h3>父组件</h3>
        <div class="content">
            <Game>
                <template v-slot="params">
                    <ul>
                        <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
                    </ul>
                </template>
            </Game>
            <Game>
                <template v-slot="params">
                    <ol>
                        <li v-for="g in params.games" :key="g.id">{{ g.name }}</li>
                    </ol>
                </template>
            </Game>
            <Game>
                <!-- 作用域插槽也可以有名字 -->
                <template v-slot:default="{ games }">
                    <h4 v-for="g in games" :key="g.id">{{ g.name }}</h4>
                </template>
            </Game>
        </div>
    </div>
</template>

<script setup lang="ts" name="Father">
    import Game from './Game.vue'
</script>

子组件

<template>
    <div class="game">
        <h2>游戏列表</h2>
        <slot :games="games"></slot>
    </div>
</template>

<script setup lang="ts" name="Game">
    import { reactive } from 'vue'
    let games = reactive([
        { id: 'asgytdfats01', name: '英雄联盟' },
        { id: 'asgytdfats02', name: '王者农药' },
        { id: 'asgytdfats03', name: '红色警戒' },
        { id: 'asgytdfats04', name: '斗罗大陆' },
    ])
</script>

总结

在这里插入图片描述

  • 35
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值