前言
在日常的 vue3 项目开发中,博主比较熟练使用 pinia 作状态管理来实现组件间的通讯,从而有些疏于对传统方法(props & emits)的练习,导致在某次项目开发过程中耽误了很多的时间。
网上现有的关于 props & emits 的教程大多是千篇一律,演示万变不离其宗的案例加之模糊的套话讲解,导致这些教程很难在人脑中留下印象。
笔者在生活中较为推崇「模型记忆法」,即将复杂的、晦涩的、不成体系的内容层层解析,绑定进一个具象的、形象的模型中,以实现轻松记忆和灵活应用。得益于「模型记忆法」,笔者将 props & emits 归纳成了「餐厅模型」,特在此记录,并希望能帮助有需要的人轻松理解 props & emits 的应用场景及方式。
餐厅模型
Props(父向子传递通信消息)
设想一下,你是一个饥肠辘辘的顾客,你走进了一家餐厅。
在这里,由子组件扮演顾客,由父组件扮演餐厅。
顾客想吃东西,但这家餐厅非常严格,只允许顾客点几个指定的菜,这些菜都写在菜单里给顾客看。
「指定的菜」即父组件通过 props 暴露给子组件的数据,这些数据在父组件里已经被规定好了。
<template>
<!-- 写在父组件里 也就是餐厅里 -->
<顾客 火山飘雪 = "西红柿炒鸡蛋" 佛山无影脚 = "卤猪蹄" :随便 = "餐厅决定" />
<Son const1 = "value1" const2 = "value2" :variable = "refValue" />
</template>
<script>
const 餐厅决定 = ref("锅包肉");
const variable = ref("refValue");
</script>
我们可以看出,顾客看到的菜名都是花里胡哨的,而餐厅知道这些菜的本质;客人不知道吃什么的时候,餐厅会拿出“餐厅决定”的菜给客人吃,默认是锅包肉。
在代码中依然是如此,变量名和值不一定相等。
顾客翻看菜单,里面每道菜旁边都有一个按钮。
「菜单」即子组件创建了一个 defineProps 对象,「菜」就是里面的属性,是父组件暴露给子组件的。
<script>
// 写在子组件里 也就是顾客手里
const 点菜 = 菜单({
火山飘雪: {
种类: "素菜",
默认: "西红柿炒鸡蛋",
},
佛山无影脚: {
种类: "荤菜",
默认: "猪蹄",
},
随便: {
种类: "荤菜",
默认: "锅包肉",
},
});
</script>
<script>
// 写在子组件里 也就是顾客手里
const props = defineProps({
const1: {
type: String,
default: "value1",
},
const2: {
type: Number,
default: "value2",
},
variable: {
type: "Function",
默认: "refValue",
},
});
</script>
顾客的大脑飞速转动后,决定点一份“佛山无影脚”堂食,再点一份“火山飘雪”打包带走,然后通过打听得知了餐厅今天的“随便”是什么的信息。
props 里的值现在就可以被子组件使用了。
<template>
<!-- 写在子组件里 也就是顾客手里 -->
<餐桌 :打包 = "菜单.西红柿炒鸡蛋" :堂食 = "菜单.卤猪蹄" />
<div :useValue1 = "props.const1" useValue2 = "props.const2" />
</template>
<script>
const 信息 = 菜单.随便;
const useValue3 = props.variable;
</script>
Emits(子向父传递通信消息)
书接上文,顾客吃完饭了,想要评价一下餐厅的菜品。
餐厅有规定:如果菜品得到了差评,则直接删除该菜品;顾客想要给菜品评价,只需要按下菜单上对应菜品旁边的“好评”或“差评”按钮。
<script>
// 写在子组件里
const 顾客评价 = 可用评价(["好评", "差评"]);
const emits = defineEmits(["positive", "negative"]);
</script>
而餐厅这边,则是会关注用户点下了“好评”还是“差评”,并根据对应的评价给出不同的响应。
父组件根据监听到的句柄去执行对应的方法。
<template>
<!-- 写在父组件里 也就是餐厅里 -->
<顾客 @好评 = "感谢好评" @差评 = "抱歉差评" />
<Son @positive = "thank" @negative = "sorry" />
</template>
<script>
规定 感谢好评() {
餐厅回应("感谢您的好评!");
}
function thank() {
console.log("感谢您的好评!");
}
规定 抱歉差评() {
删除(菜品编号);
餐厅回应("抱歉给您带来了不好的用餐体验!");
}
function sorry() {
delete(value.Id);
console.log("抱歉给您带来了不好的用餐体验!");
}
</script>
实际上,根据上面的描述,我们可以看出,顾客没有直接删除菜品的权力,而是顾客通过评价“通知”餐厅,而餐厅去执行删去菜品的动作。
在代码中也是如此,子组件并没有实际给父组件“传递”什么,而是通过定义“事件”的句柄,然后将“事件”的句柄向上“冒泡”,由父组件监听冒泡的事件句柄,然后去执行对应的操作,是一个类似“通知”的过程。
因此,笔者认为很多教程中提到的「Emits 是子组件给父组件“传值”」是不准确的。