vue3组件间通信

135 阅读4分钟

一、props

概述:props是使用频率最高的一种通信方式,常用与:父<->子 若父传子:属性值是非函数 若子传父:属性值是函数

Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4>汽车:{{car}}</h4>
        <h4 v-show="toy">子给的玩具:{{toy}}</h4>
        <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) {
        console.log('父', value)
        toy.value = value
    }
</script>

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="Father">
    import Child from './Child.vue'
    import { ref } from 'vue'
    // 数据
    let toy = ref('变形金刚')
    //声明接收props
    defineProps(['car','sendToy'])
</script>

二、emit 自定义事件

Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4>子给的玩具:{{toy}}</h4>
        <!-- 给子组Child绑定事件-->
        <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) {
        console.log('saveToy',value)
        toy.value = value
    }
</script>

Child.vue

<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, onMounted } from 'vue'
    let toy = ref('变形金刚')
    // 声明事件
    const emit = defineEmits(['send-toy'])
    onMounted(() => {
        setTimeout(() => {
            emit('send-toy', toy.value)
        }, 3000)
    })
</script>

三、mitt

1.首先要安装mitt npm i mitt 2.创建uitls文件下的emitter.ts

emitter.ts

//引用mitt
import mitt from 'mitt'
//调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter= mitt()
//绑定事件
emitter.on('test1', () => {
    console.log('test1被调用了')
})
emitter.on('test2', () => {
    console.log('test2被调用了')
})
//触发事件
setInterval(() => {
    emitter.emit('test1')
    emitter.emit('test2')
},1000)
//解绑事件
setTimeout(() => {
    //emitter.off('test1')
    //emitter.off('test2')
    emitter.all.clear()
},3000)
export default emitter

3.在main.ts文件中引用emitter.ts

import emitter from '@/utils/emitter'

Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <Child1/>
        <Child2/>
    </div>
</template>
<script setup lang="ts" name="Father">
    import Child1 from './Child1.vue'
    import Child2 from './Child2.vue'
</script>

Child1.vue

<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, onMounted } from 'vue'
    import emitter from '@/utils/emitter'
    let toy = ref('变形金刚')
</script>

Child2.vue

<template>
    <div class="child2">
        <h3>子组件2</h3>
        <h4>电脑: {{computer}}</h4>
        <h4>弟弟接收哥哥给的玩具: {{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: string) => {
        toy.value = value
    })
    //在组件卸载时解绑send-toy事件,为了是当Child1组件没有了就不存在send-toy事件,更是为了占用内存空间
    onUnmounted(() => {
        emitter.off('send-toy')
    })
</script>

四、v-model

Father.vue

<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用在组件标签上-->
        <!-- <Child v-model="username"/>  第一种方法-->
        <Child :modelValue="username" @update:modelValue="username=$event"/> <!--第二种方法-->
    </div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
    import { ref } from "vue"
    //数据
    let username = ref('zhangsan')
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件1</h3>
        <input type="text" :value="modelValue" @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)">
    </div>
</template>
<script setup lang="ts" name="Child">
    defineProps(['modelValue'])
    const emit = defineEmits(['update:modelValue'])
</script>

event到底是啥?啥时候能.target对于原生事件,event到底是啥?啥时候能.target 对于原生事件,event就是事件对象======> 能.target 对于自定义事件,$event就是触发事件时,所传递的数据 ==> 不能.target Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <!--修改modelValue-->
        <Child v-model:ming="username" v-model:mima="password"/>
    </div>
</template>
<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref } from "vue"
    //数据
    let username = ref('zhangsan')
    let password = ref('123456')
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件1</h3>
        <input type="text" :value="ming" @input="emit('update:ming',(<HTMLInputElement>$event.target).value)">
        <input type="text" :value="mima" @input="emit('update:mima',(<HTMLInputElement>$event.target).value)">
    </div>
</template>
<script setup lang="ts" name="Child">
    defineProps(['ming','mima'])
    const emit = defineEmits(['update:ming', 'update:mima'])
</script>

五、$attrs

1.概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信(祖-> 孙)
2.具体说明:$attrs是一个对象,包含所有父组件传入的标签属性
注意:$attrs会自动排除props中声明的属性(可以认为声明过的props被子组件自己“消费”了)

Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <Child :a="a" :b="b" v-bind="{x:10,y:20}" :update="update/>
    </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)
    function update(value: number) {
        a.value += value
    }
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件</h3>
        <GrandChild v-bind="$attrs">
    </div>
</template>
<script setup lang="ts" name="Child">
    import GrandChild from './GrandChild.vue'
</script>

GrandChild.vue

<template>
    <div class="GrandChild">
        <h3>孙组件</h3>
        <h4>a:{{a}}</h4>
        <h4>a:{{b}}</h4>
        <h4>x:{{x}}</h4>
        <h4>y:{{y}}</h4>
        <button @click="update(2)">点我将爷爷那边的a更新</button>
    </div>
</template>
<script setup lang="ts" name="Child">
    defineProps(['a','b', 'x','y', 'update'])
</script>

$refs和$params

1.概述:

  • $refs用于:父 -> 子
  • $parent用于:子 -> 父

2.原理如下:

属性说明
$refs值为对象,包含所有被ref属性标识的DOM元素或组件实例
$parent值为对象,当前组件的父组件实例对象

Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <h4>房子: {{house}}套</h4>
        <button @click="changeToy">修改child1的玩具</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 house = ref(1)
    let c1= ref()
    let c2 = ref()
    //方法
    function changeToy() {
        console.log(c1.value)
        c1.value.toy= "奥特曼"
    }
    function getAllChild(refs:{[key:string]:any}) {
        for(let key in refs) {
            console.log(refs[key])
            refs[key].book += 3
        }
    }
    //把数据交给外部
    defineExpose({house})
</script>

Child1.vue

<template>
    <div class="child1">
        <h3>子组件1</h3>
        <h4>玩具: {{toy}}</h4>
        <h4>书籍: {{book}}本</h4>
    </div>
</template>
<script setup lang="ts" name="Child1">
    import { ref, onMounted } from 'vue'
    import emitter from '@/utils/emitter'
    let toy = ref('变形金刚')
    let book = ref(3)
    //把数据交给外部
    defineExpose({toy, book})
</script>

Child2.vue

<template>
    <div class="child2">
        <h3>子组件2</h3>
        <h4>电脑: {{computer}}</h4>
        <h4>书籍: {{book}}本</h4>
        <button @click="minusHouse($parent)">干掉父亲的一套房产</button>
    </div>
</template>
<script setup lang="ts" name="Child1">
    import { ref, onMounted } from 'vue'
    import emitter from '@/utils/emitter'
    let computer = ref('联想')
    let book = ref(4)
    //方法
    function minusHouse(parent:any) {
        console.log(parent)
        parent.house -= 1
    }
    //把数据交给外部
    defineExpose({computer, book})
</script>

七、provide、inject

1.概述:

  • 实现祖孙租金直接通信

2.具体使用:

  • 在祖先组件中通过provide配置向后代组件提供数据
  • 在后代组件中通过injet配置来声明接收数据

3.具体编码:

Father.vue

<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(1)
    let car = reactive({
        brand: '宝马',
        price: 100
    })
    function updateMoney(value: number) {
        money.value -= value
    }
    //向后代提供数据
    //provide('qian', money)
    provide('moneyContext', {money, updateMoney})
    provide('che', car)
    
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件</h3>
        <GrandChild />
    </div>
</template>
<script setup lang="ts" name="Child">
    import GrandChild from './GrandChild.vue'
</script>

GrandChild.vue

<template>
    <div class="GrandChild">
        <h3>孙组件</h3>
        <h4>定金:{{money}}万元</h4>
        <h4>车子:一辆{{car.brand}},价格{{car.price}}万元</h4>
        <button @click="updateMoney(2)">花爷爷的钱</button>
    </div>
</template>
<script setup lang="ts" name="Child">
    import {inject} from "vue"
    //let money = inject('qian', '我是默认值如:1')
    let {money,updateMoney} = inject('moneyContext', {money: 1, updateMoney:(param:number) => {}})
    let car = inject('che', {brand: '未知', price: 0})
</script>

八、pinia

九、slot

第一种:默认插槽 默认插槽也有名字<slot name="default"></slot>

Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <div class="content">
            <Child title="热门游戏列表">
                <ul>
                    <li v-for="g in games" :key="g.id">{{g.name}}</li>
                </ul>
            </Child>
            <Child title="今日美食城市">
                <img :src="imgUrl" alt=""/>
            </Child>
            <Child title="今日影视推荐">
                <video :src="videoUrl" controls></video>
            </Child>
        </div>
        
    </div>
</template>
<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref, reactive } from "vue"
    //数据
    let game = reactive([
        {id: '01', name: '王者荣耀'},
        {id: '02', name: 'cs'},
    ])
    let imUrl = ref('https://12.png')
    let videoUrl = ref('https://23.mp4')
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件</h3>
        <h4>{{title}}</h4>
        <slot>默认内容</slot>
    </div>
</template>
<script setup lang="ts" name="Child">
    defineProps(['title'])
</script>

第二种具名插槽:具有名字的插槽 Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <div class="content">
            <Child>
                <template v-slot:s2>
                    <ul>
                        <li v-for="g in games" :key="g.id">{{g.name}}</li>
                    </ul>
                </template>
                <template #s1>
                    <h2>热门游戏列表</h2>
                </template>
            </Child>
        </div>
        
    </div>
</template>
<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { reactive } from "vue"
    //数据
    let game = reactive([
        {id: '01', name: '王者荣耀'},
        {id: '02', name: 'cs'},
    ])
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件</h3>
        <slot name="s1">默认内容1</slot>
        <slot name="s2">默认内容2</slot>
    </div>
</template>
<script setup lang="ts" name="Child">
</script>

第三种作用域插槽 作用域插槽可以有名字 <template v-slot:qwe="params"> Father.vue

<template>
    <div class="father">
        <h3>父组件</h3>
        <div class="content">
            <Child>
                <template v-slot:qwe="params">
                    <ul>
                        <li v-for="g in params.youxi" :key="g.id">{{g.name}}</li>
                    </ul>
                </template>
            </Child>
            <Child>
                <template v-slot="params">
                    <ol>
                        <li v-for="g in params.youxi" :key="g.id">{{g.name}}</li>
                    </ol>
                </template>
            </Child>
            <Child>
                <template v-slot="{youxi}">
                    <h3 v-for="g in youxi" :key="g.id">{{g.name}}</h3>
                </template>
            </Child>
        </div>
        
    </div>
</template>
<script setup lang="ts" name="Father">
    import Child from './Child.vue'
    import { ref, reactive } from "vue"
    //数据
    let game = reactive([
        {id: '01', name: '王者荣耀'},
        {id: '02', name: 'cs'},
    ])
</script>

Child.vue

<template>
    <div class="child">
        <h3>子组件</h3>
        <!-- <slot :youxi="game">默认内容</slot> -->
        <slot name="qwe" :youxi="game">默认内容</slot>
    </div>
</template>
<script setup lang="ts" name="Child">
 import { reactive } from "vue"
    //数据
    let game = reactive([
        {id: '01', name: '王者荣耀'},
        {id: '02', name: 'cs'},
    ])
</script>