前言
  在日常的 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 是子组件给父组件“传值”」是不准确的。