Vue3中组件通信的九种方式
一、props:父传子二、自定义事件:子传父三、mitt:任意组件通信四、v-model:父子互传,用的较少五、$attrs:爷孙互传六、$refs与$parent七、provide、inject:祖给后代们八、pinia九、slot(插槽)
一、props:父传子
也可用于子传父,前置条件是父先给子一个函数
1. 父组件Father.vue
<template>
<h2>父组件</h2>
<h4>车子:{{ car }}</h4>
<h4 v-show="toy">子组件给的数据:{{ toy }}</h4>
<Child :sendToy="getToy" :parents="parents"/>
</template>
<script setup lang="ts">
// 子组件
import Child from './Child.vue'
import {ref, reactive} from 'vue'
// 数据
let parents = reactive({
name: "张三",
age: 20
})
let toy = ref('')
// 此方法给传递给子组件调用传参给父
function getToy(value: string) {
toy.value = value
}
</script>
2. 子组件Child.vue
<template>
<h2>子组件</h2>
<h2>父给的数据:{{ parents }}</h2>
<button @click="sendToy(toy)">把 toy 给父</button>
</template>
<script setup lang="ts">
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明接收props
defineProps(['car', 'sendToy', "parents"])
</script>
二、自定义事件:子传父
1. 父组件Father.vue
<template>
<h2>父组件</h2>
<h3 v-show="toy">子给的数据:{{ toy }}</h3>
<!-- 给子组件Child绑定自定义事件 -->
<Child @send-toy="saveToy"/>
</template>
<script setup lang="ts">
import Child from './Child.vue'
import {ref} from "vue";
// 数据
let toy = ref('')
// 用于保存传递过来的数据
function saveToy(value: string) {
toy.value = value
}
</script>
2. 子组件Child.vue
<template>
<h2>子组件</h2>
<h3>玩具:{{ toy }}</h3>
<button @click="emit('send-toy',toy)">把玩具传给父</button>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
// 数据
let toy = ref('奥特曼')
// 声明事件
const emit = defineEmits(['send-toy'])
</script>
三、mitt:任意组件通信
1. 安装
npm i mitt
2. 创建文件:src\utils\emitter.ts
// 引入mitt
import mitt from 'mitt'
// 调用mitt得到emitter,emitter能:绑定事件、触发事件
const emitter = mitt()
export default emitter
3. Child1.vue传递数据
<template>
<div class="child1">
<h2>子组件1</h2>
<h3>玩具:{{ toy }}</h3>
// 触发send-toy事件,并传递数据toy
<button @click="emitter.emit('send-toy',toy)">传递toy给监听send-toy事件的组件</button>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import emitter from '@/utils/emitter';
let toy = ref('奥特曼')
</script>
4. Child2.vue接收数据
<template>
<div class="child2">
<h2>子组件2</h2>
<h3>电脑:{{ computer }}</h3>
<h3>Child1.vue给的数据:{{ toy }}</h3>
</div>
</template>
<script setup lang="ts">
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:父子互传,用的较少
1. 先理解v-model底层如何实现
<template>
<input type="text" v-model="username">
等价于:
<input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value">
// <HTMLInputElement>是为了ts类型检查需加上,否则会报红
</template>
2. 假如我要封装一个自己的<el-input>要怎么实现?
相信大家都用过 <el-input v-model="username">
上面第1点中<input type="text" v-model="username">就是给input绑定一个自定义事件实现双向绑定,其实组件也是如此。
<el-input v-model="username"/>
等价于:
<el-input :modelValue="username" @update:modelValue="username = $event"/>update:modelValue其实就是一个自定义事件名,这是vue规定的
$event就是触发自定义事件传递过来的数据
这也就意味着我封装自己的<el-input>也是如此
<MyInput v-model="username"/>
等价于:
<MyInput :modelValue="username" @update:modelValue="username = $event"/>
3. 那么MyInput组件内部要怎么写呢?
<template>
// 2. 像第一点那样展示数据,绑定自定义事件即可
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)"
>
</template>
<script setup lang="ts" >
// 1. 先接收数据与自定义事件
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
4. <MyInput v-model="username"/> 怎么传递两个参数?
<MyInput v-model:ming="username" v-model:mima="password"/>
等价于:
<MyInput
:ming="username"
@update:ming="username = $event"
:mima="password"
@update:mima="password= $event"
/>
其实就是改了modelValue
5. 接收时不变
<template>
<input
type="text"
:value="ming"
@input="emit('update:ming',(<HTMLInputElement>$event.target).value)"
>
<br>
<input
type="text"
:value="mima"
@input="emit('update:mima',(<HTMLInputElement>$event.target).value)"
>
</template>
<script setup lang="ts" >
defineProps(['ming','mima'])
const emit = defineEmits(['update:ming','update:mima'])
</script>
五、$attrs:爷孙互传
1. 父组件:Father.vue
<template>
<h4>a:{{ a }}</h4>
<Child :a="a" v-bind="{x:100,y:200}" :updateA="updateA"/>
// v-bind="{x:100,y:200}" 等于 :x="100" :y="200"
</template>
<script setup lang="ts">
import Child from './Child.vue'
import {ref} from 'vue'
let a = ref(1)
function updateA(value: number) {
a.value += value
}
</script>
2. 子组件Child.vue
父给子传了数据但子没接收,数据会保存到 $attrs 中
<template>
<GrandChild v-bind="$attrs"/>
</template>
<script setup lang="ts">
import GrandChild from './GrandChild.vue'
</script>
3. 孙组件GrandChild.vue
<template>
<h4>a:{{ a }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(6)">给爷爷传递数据</button>
</template>
<script setup lang="ts">
defineProps(['a','x', 'y', 'updateA'])
</script>
六、$refs与$parent
$refs:父传子,$refs获取所有子组件的实例对象
$parent:子传父,$parent 获取父组件的实例
前提是需要用defineExpose({...数据})将数据提供给外部
1. 父组件Father.vue
<template>
<h3>房产:{{ house }}</h3>
<button @click="changeToy($refs)">修改Child1的数据</button>
<Child1 ref="c1"/>
</template>
<script setup lang="ts">
import Child1 from './Child1.vue'
import {ref, reactive} from "vue";
let house = ref(4)
// $refs获取所有子组件的实例对象
function changeToy(refs: { [key: string]: any }) {
refs.c1.book += 1
}
// 向外部提供数据
defineExpose({house})
</script>
2. 子组件Child1.vue
<template>
<h3>书籍:{{ book }} 本</h3>
<button @click="minusHouse($parent)">修改父组件的数据</button>
</template>
<script setup lang="ts">
import {ref} from "vue";
let book = ref(3)
// $parent 获取父组件的实例
function minusHouse(parent: any) {
parent.house -= 1
}
// 把数据交给外部
defineExpose({toy, book})
</script>
七、provide、inject:祖给后代们
1. 父组件Father.vue
<script setup lang="ts">
import {ref, reactive, provide} from 'vue'
let money = ref(100)
let car = reactive({
brand: '宝马',
price: 10
})
function updateMoney(value: number) {
money.value -= value
}
// 向后代提供数据
provide('moneyContext', {money, updateMoney})
provide('car', car)
</script>
2. 后代组件GrandChild.vue
<script setup lang="ts">
import {inject} from "vue";
第一个参数与provide的第一个参数一致
第二个可选,设置默认值,也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。
第三个可选,类型为布尔值。当第二个参数的值就是一个函数,而不是工厂函数时,需要使用将值设置为 false。
let {money, updateMoney} = inject('moneyContext', {
money: 0, updateMoney: (param: number) => {
}
})
let car = inject('car', {brand: '未知', price: 0})
</script>
八、pinia
可查看之前写的文章:pinia的基本使用,从入门到精通-CSDN博客
九、slot(插槽)
1. 默认插槽
<Category>
<h1>张三<h1>
</Category>
Category组件中:
<template>
<slot>默认内容</slot>
</template>
2. 具名插槽
<Category>
<template #s2>
<h2>李四</h2>
</template>
<template #s1>
<h2>张三</h2>
</template>
</Category>
Category组件中:
<template>
<slot name="s1">默认内容1</slot>
<slot name="s2">默认内容2</slot>
</template>
3. 作用域插槽
<Game>
// qwe是组件中slot的name属性
// params是组件传递过来的数据,是一个对象
<template v-slot:qwe="params">
<ul>
<li v-for="y in params.games" :key="y.id">
{{ y.name }}
</li>
</ul>
</template>
// #defaule是将内容放入默认插槽
<template #defaule="params">
<h2>{{ params.title }}</h2>
</template>
</Game>
Game组件中:
<template>
<slot :title="title"></slot>
<slot name="qwe" :games="games"></slot>
</template>
<script setup lang="ts">
import {reactive,ref} from 'vue'
let title= ref("游戏列表")
let games = reactive([
{id:'1',name:'lol'},
{id:'2',name:'王者'},
{id:'3',name:'吃鸡'}
])
</script>
更多推荐
所有评论(0)