05 Vue3组件间的通信-props、defineEmits、mitt、v-model、$attrs、$ref和$parent
Vue3
组件通信和Vue2
的区别:
- 移出事件总线,使用
mitt
代替。
-
vuex
换成了pinia
-
把
.sync
优化到了v-model
里面了 -
把
$listeners
所有的东西,合并到$attrs
中了 -
$children
被砍掉了
常见搭配形式
props - 【父传子 子传父】
若 父传子:属性值是非函数
若 子传父:属性值是函数
一般都用于 父传子
父传子
父组件
<template>
<Child :car="car" :obj="obj" :list="list" />
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import Child from '@/components/Child.vue';
const car = ref('奔驰');
// 定义数据类型-对象
type personsType = {
id: string;
name: string;
age?: number;
};
// 定义数据类型-数组
// type ArrayPer = Array<persons>;
type ArrayPer = personsType[];
let obj = reactive<personsType>({ id: 'asdfj01', name: '张三' });
let list = reactive<ArrayPer>([
{ id: 'asdfj01', name: '张三', age: 10 },
{ id: 'asdfj02', name: '李四' },
{ id: 'asdfj03', name: '王五' },
]);
</script>
子组件-接收数据
<template>
<h1>接收来自父组件的值:</h1>
数值: {{ car }}
<hr />
对象:{{ obj }}
<hr />
数组:{{ list }}
<hr />
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
// 定义数据类型-对象
type personsType = {
id: string;
name: string;
age?: number;
};
// 方式一:直接接收
// defineProps(['car','obj','list']);
// 方式一:设置数据类型
defineProps<{
car: string;
obj: personsType;
list: personsType[];
}>();
</script>
子传父
父组件
<template>
# 子传父:属性值是 函数
<Child :getMsg="getMsg" />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
function getMsg(val: string) {
console.log('收到了来自子组件的数据:' + val);
}
</script>
子组件
<template>
<h1>点击按钮向父组件发送数据:</h1>
<button @click="sentMsg">传送数据</button>
<hr />
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
// 方式一:直接接收
let props = defineProps(['getMsg']);
// // 方式一:设置数据类型
// let props = defineProps<{
// getMsg: Function;
// }>();
function sentMsg() {
props.getMsg('哈哈哈');
}
</script>
自定义事件--【子传父】 defineEmits
父组件--接收数据
<template>
// customerEventName 子组件里面的 自定义事件
<Child @customerEventName="getMsg" />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
function getMsg(val: string) {
console.log('收到了来自子组件的数据:' + val);
}
</script>
子组件-发送数据
<template>
<h1>点击按钮向父组件发送数据:</h1>
<button @click="sentMsg">传送数据</button>
<hr />
</template>
<script lang="ts" setup>
// customerEventName 是自定义事件的名称
const emit = defineEmits(['customerEventName']);
function sentMsg() {
const msg = '哈哈哈';
// 向父组件传递参数
emit('customerEventName', msg);
}
</script>
mitt-- 任意组件间通信
与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信
1. 安装
npm i mitt
2. 构建 src\utils\emitter.ts
// 引入mitt
import mitt from 'mitt';
// 创建emitter
const emitter = mitt();
// 创建并暴露mitt
export default emitter;
3. mitt 绑定on-调用-发布-接收数据
和 触发emit-定义-订阅-发数据
事件
// emitter表示的是 emitter对象
// 绑定事件--调用事件
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();
// 移除事件
emitter.off('abc')
}, 3000);
4. 一个案例
父组件--监听事件--接收数据
<template>
<Child />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { onMounted, onUnmounted } from 'vue';
import mittBus from '@/utils/emitter';
onMounted(() => {
getMsg();
});
onUnmounted(() => {
mittBus.off('customerEventName');
});
const getMsg = () => {
mittBus.on('customerEventName', (val) => {
console.log('收到了来自子组件的数据:' + val);
});
};
</script>
子组件--触发事件--发数据
<template>
<h1>点击按钮向父组件发送数据:</h1>
<button @click="sentMsg">传送数据</button>
<hr />
</template>
<script lang="ts" setup>
import mittBus from '@/utils/emitter';
// customerEventName 是自定义事件的名称
const sentMsg = () => {
const msg = '哈哈哈';
// 向父组件传递参数msg
mittBus.emit('customerEventName', msg);
};
</script>
v-model -- 父传子 子传父
v-model
的双向数据绑定
v-model的双向数据绑定本质就是 input 绑定一个value值,同时监听input的value变化,重新赋值
<template>
<h2>父组件</h2>
<input type="text" v-model="username" />
<!-- v-model的双向数据绑定本质就是 input 绑定一个value值,同时监听input的value变化,重新赋值 -->
<input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
let username = ref('zhangsan');
</script>
组件标签上的v-model
的本质
:moldeValue
+ update:modelValue
事件
<!-- 通过 v-model 将username 传递给 Child 子组件 -->
<Child v-model="username" />
<!-- 组件标签上v-model的本质 -->
<!-- 传递数据modelValue,同时绑定事件 update:modelValue -->
<Child :modelValue="username" @update:modelValue="username = $event" />
父子互相通信
父组件--发送数据
<template>
<h2>父组件</h2>
<input type="text" v-model="username" />
<!-- 通过 v-model 将username 传递给 Child 子组件 -->
<Child v-model="username" />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let username = ref('zhangsan');
</script>
子组件-接收数据
通过 defineProps 接收 名为 modelValue
的数据,就是父组件传递过来的 username
<template>
<hr />
<h2>子组件</h2>
<h3>通过v-model接收父组件的数据:</h3>
{{ modelValue }}
<input type="text" :value="modelValue" @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)" />
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
// 接收数据 modelValue
defineProps(['modelValue']);
// 自定义触发事件 pdate:modelValue
const emit = defineEmits(['update:modelValue']);
</script>
更换 modelValue 的命名
父组件
<template>
<h2>父组件</h2>
<input type="text" v-model="username" />
<input type="text" v-model="pwd" />
<!-- 通过 v-model 将username 传递给 Child 子组件 -->
<Child v-model:abc="username" v-model:xyz="pwd" />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let username = ref('zhangsan');
let pwd = ref('12356');
</script>
子组件
<template>
<hr />
<h2>子组件</h2>
<h3>通过v-model接收父组件的数据:</h3>
{{ abc }}=={{ xyz }}
<input type="text" :value="abc" @input="emit('update:abc', (<HTMLInputElement>$event.target).value)" />
<input type="text" :value="xyz" @input="emit('update:xyz', (<HTMLInputElement>$event.target).value)" />
</template>
<script lang="ts" setup>
import { defineProps } from 'vue';
// 接收数据 modelValue
defineProps(['abc', 'xyz']);
// 自定义触发事件 pdate:modelValue
const emit = defineEmits(['update:abc', 'update:xyz']);
</script>
$attrs -- 祖孙间传递数据
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)
$attrs
是一个对象,包含所有父组件传入的标签属性
注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
父组件
<template>
<h2>父组件</h2>
<h3>a={{ a }}</h3>
<h3>b={{ b }}</h3>
<h3>c={{ c }}</h3>
<h3>num={{ num }}</h3>
<Child :a="a" :b="b" :c="c" v-bind="{ x: 100, y: 200, z: 300 }" :updateSum="updateSum" />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref } from 'vue';
let num = ref(1);
let a = ref('a');
let b = ref('b');
let c = ref('c');
const updateSum = (val: number) => {
num.value = val;
};
</script>
子组件 : 子组件不做任何事情,直接将 $attrs 传递给孙子组件
子组件可以通过 useAttrs
方法获取到组件标签标签上面传递过来的所有数据和方法
import { useAttrs } from 'vue';
const $attrs = useAttrs();
console.log($attrs);
<template>
<hr />
<h2>子组件不做任何事情,直接将 $attrs 传递给孙子组件</h2>
<GrandChild v-bind="$attrs" />
</template>
<script lang="ts" setup>
import GrandChild from '@/components/GrandChild.vue';
</script>
孙组件
<template>
<hr />
<h2>孙子组件</h2>
<h3>a={{ a }}</h3>
<h3>b={{ b }}</h3>
<h3>c={{ c }}</h3>
<h3>x={{ x }}</h3>
<h3>y={{ y }}</h3>
<h3>z={{ z }}</h3>
<button @click="updateSum(666)">点击将 num 的值 改为 666</button>
</template>
<script lang="ts" setup>
defineProps(['a', 'b', 'c', 'x', 'y', 'z', 'updateSum']);
</script>
$refs
子传父 $parent
父传子
$refs
子传父,结合ref组件标签
$refs
值为对象,包含所有被ref
属性标识的DOM
元素或组件实例
父组件:只可以修改 子组件向外暴露的数据
<template>
<h2>父组件</h2>
<!-- $refs 子组件所有的 实例对象 -->
<button @click="getAllChilds($refs)">获取所有子组件的实例对象,并修改数据</button>
<Child1 ref="c1" />
<Child2 ref="c2" />
</template>
<script lang="ts" setup>
import Child1 from '@/components/Child1.vue';
import Child2 from '@/components/Child2.vue';
import { ref } from 'vue';
let c1 = ref();
let c2 = ref();
function getAllChilds(refs: any) {
refs.c1.name = '张三三';
refs.c2.name = '李四四';
}
</script>
子组件:必须向外暴露数据,父组件才可以修改和获取
子组件1
<template>
<hr />
<h2>子组件-Child1-{{ name }}</h2>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('张三');
// 将name向外暴露出去
defineExpose({ name });
</script>
子组件2
<template>
<hr />
<h2>子组件-Child2-{{ name }}</h2>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
let name = ref('李四');
// 将name向外暴露出去
defineExpose({ name });
</script>
$parent
父传子
$parent
值为对象,当前组件的父组件实例对象
父组件
<template>
<h2>父组件</h2>
数值num为=={{ num }}
<Child1 />
</template>
<script lang="ts" setup>
import Child1 from '@/components/Child1.vue';
import { ref } from 'vue';
let num = ref(6);
// 必须将父组件的值暴露给子组件,子组件才可以修改父组件的值
defineExpose({ num });
</script>
子组件
<template>
<hr />
<h2>子组件-Child1</h2>
<button @click="minus($parent)">将父组件num值减1</button>
</template>
<script lang="ts" setup>
function minus(p: any) {
console.log(p.num);
p.num -= 1;
}
</script>
provide、inject 祖孙间传递数据
实现祖孙组件直接通信
- 在祖先组件中通过
provide
配置向后代所有组件提供数据 - 在后代组件中通过
inject
配置来声明接收数据
父组件
<template>
<h2>父组件</h2>
{{ num }}==={{ person }}
<Child />
</template>
<script lang="ts" setup>
import Child from '@/components/Child.vue';
import { ref, reactive, provide } from 'vue';
// 定义数据
let num = ref(6);
let person = reactive({
name: '张三',
age: 29,
});
// 更新num的方法
function updateNum(val: number) {
num.value -= val;
}
// 父组件传值给子组件:通过 【provide】向所有后代组件提供数据
provide('moneyContext', { num, updateNum }); // 对象形式的传值
provide('personObj', person); // 直接传值
</script>
<style scoped lang="scss"></style>
子组件
<template>
<hr />
<h2>子组件</h2>
{{ num }}=={{ personObj }}
<button @click="updateData">将父组件num值减1</button>
</template>
<script lang="ts" setup>
import { inject } from 'vue';
// 接收父组件的传值,可以设置默认值
let { num, updateNum } = inject('moneyContext', { num: 0, updateNum: (X: number) => {} });
let personObj = inject('personObj', { name: '我是默认值', age: 20 });
// 子组件传值给父组件 更新num的值
function updateData() {
updateNum(1);
}
</script>
Pinia
参考:
Pinia状态管理
插槽
参考: