Vue3 组件通信和 Vue2 的区别:

  • 移出事件总线,使用 mitt 代替
  • vuex 换成了 pinia
  • .sync 优化到了 v-model 里面了
  • $listeners 所有的东西,合并到 $attrs 中了
  • $children 被砍掉了

常见搭配形式:

image-20231119185900990

# props

使用频率最高的一种通信方式,常用于 :父 ⇆ 子

  • 父传子:属性值是非函数
  • 子传父:属性值是函数
示例

父组件

<template>
  <div class="father">
    <h3>父组件</h3>
    <div>父组件的值:</div>
    <div v-show="text">接收:</div>
    <Child :msg="msg" :sendText="getText" />
  </div>
</template>
<script setup lang="ts" name="Father">
  import Child from "./Child.vue";
  import { ref } from "vue";
  let msg = ref("父传子");
  let text = ref("");
  // 函数接收子组件传来的值
  function getText(value: string) {
    text.value = value;
  }
</script>

子组件

<template>
  <div class="child">
    <h3>子组件</h3>
    <div>子组件的值:</div>
    <div>接收:</div>
    <button @click="sendText(text)">子传父</button>
  </div>
</template>
<script setup lang="ts" name="Child">
  import { ref } from "vue";
  let text = ref("子传父");
  defineProps(["msg", "sendText"]);
</script>

# 自定义事件

自定义事件常用于:子 ➡︎ 父

注意区分好:原生事件、自定义事件

  • 原生事件
    • 事件名是特定的( clickmosueenter 等等)
    • 事件对象 $event :是包含事件相关信息的对象( pageXpageYtargetkeyCode
  • 自定义事件
    • 事件名是任意名称
    • 事件对象   $event :是调用   emit 时所提供的数据,可以是任意类型!!!
<!-- 在父组件中,给子组件绑定自定义事件:-->
<Child @send-toy="toy = $event" />
<!-- 注意区分原生事件与自定义事件中的 $event-->
<button @click="toy = $event">测试</button>
示例

父组件

<template>
  <div class="father">
    <h3>父组件</h3>
    <!-- 什么都不传 -->
    <!-- <button @click="test"> 点我 & lt;/button> -->
    <!-- $event 占位符:事件对象 -->
    <!-- <button @click="test (6, $event)"> 点我 & lt;/button> -->
    <div v-show="text">接收:</div>
    <!-- abc:事件名,xyz:事件回调
    <Child @abc="xyz" /> -->
    <!-- 给子组件绑定事件 -->
    <Child @send-text="getText" />
  </div>
</template>
<script setup lang="ts" name="Father">
  import Child from "./Child.vue";
  import { ref } from "vue";
  // function test(x) {
  //   // 什么都不传,传递的是 event 对象
  //   console.log ("父组件 test 方法被调用了", x);
  // }
  // function test(a: number, x: Event) {
  //   // 什么都不传,传递的是 event 对象
  //   console.log ("父组件 test 方法被调用了", a, x);
  // }
  let text = ref("");
  function getText(value: string) {
    text.value = value;
  }
</script>

子组件

<template>
  <div class="child">
    <h3>子组件</h3>
    <div>子组件的值:</div>
    <button @click="emit('sendText', text)">子传父</button>
  </div>
</template>
<script setup lang="ts" name="Child">
  import { ref, onMounted } from "vue";
  let text = ref("子传父");
  // 声明事件
  const emit = defineEmits(["sendText"]);
  onMounted(() => {
    setTimeout(() => {
      emit("sendText", "3秒后获取子组件的值"); // 子组件中,触发事件
    }, 3000);
  });
</script>

# mitt

与消息订阅与发布( pubsub )功能类似,可以实现任意组件间通信

# 安装 mitt
npm i mitt
基本使用: src\utils\emitter.ts(新建文件)
// 引入 mitt
import mitt from "mitt";
// 调用 mitt 得到 emitter,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); 
*/
// 暴露 emitter
export default emitter;
示例

接收数据的组件中:绑定事件、同时在销毁前解绑事件

import emitter from "@/utils/emitter";
import { onUnmounted } from "vue";
// 绑定事件
emitter.on("send-toy", value => {
  console.log("send-toy事件被触发", value);
});
onUnmounted(() => {
  // 解绑事件
  emitter.off("send-toy");
});

【第三步】:提供数据的组件,在合适的时候触发事件

import emitter from "@/utils/emitter";
function sendToy() {
  // 触发事件
  emitter.emit("send-toy", toy.value);
}

注意这个重要的内置关系,总线依赖着这个内置关系

# v-model

实现 父 ⇆ 子 之间相互通信

1、用在 html 标签上

<input type="text" v-model="username" />
<!-- v-model 底层原理 -->
<input
  type="text"
  :value="username"
  @input="username = (<HTMLInputElement>$event.target).value"
/>

2、用在组件标签上

本质: :moldeValue + update:modelValue 事件

<MyInput v-model="username" />
<!-- 组件标签上 v-model 的本质 -->
<MyInput :modelValue="username" @update:modelValue="username = $event" />
示例:自定义组件

MyInput 组件

<template>
  <input
    type="text"
    :value="modelValue"
    @input="emits('update:modelValue', (<HTMLInputElement>$event.target).value)"
  />
</template>
<script setup lang="ts" name="MyInput">
  // 接收 props
  defineProps(["modelValue"]);
  // 声明事件
  const emit = defineEmits(["update:model-value"]);
</script>

父组件

<template>
  <div>用户:</div>
  <MyInput v-model="username" />
</template>
<script setup lang="ts" name="Father">
  import { ref } from "vue";
  import MyInput from "./MyInput.vue";
  let username = ref("daidai");
</script>
示例:在组件标签上多次使用 v-model

MyInput 组件

<template>
  <!-- 修改 modelValue -->
  <input
    type="text"
    :value="user"
    @input="emits('update:user', (<HTMLInputElement>$event.target).value)"
  />
  <input
    type="text"
    :value="pwd"
    @input="emits('update:pwd', (<HTMLInputElement>$event.target).value)"
  />
</template>
<script setup lang="ts" name="MyInput">
  // 修改 modelValue
  defineProps(["user", "pwd"]);
  const emits = defineEmits(["update:user", "update:pwd"]);
</script>

父组件

<template>
  <MyInput v-model:user="username" v-model:pwd="password" />
</template>
<script setup lang="ts" name="Father">
  import { ref } from "vue";
  import MyInput from "./MyInput.vue";
  let username = ref("daidai");
  let password = ref("123456");
</script>

# $attrs

用于实现当前组件的父组件,向当前组件的子组件通信(祖 ➡︎ 孙

$attrs 是一个对象,包含所有父组件传入的标签属性

⚠️ 注意: $attrs 会自动排除 props 中声明的属性(可以认为声明过的 props 被子组件自己 “消费” 了)

示例

父组件

<template>
  <div class="father">
    <h3>父组件</h3>
    <div>a: ~~ b: ~~ c: ~~ d:</div>
    <Child
      :a="a"
      :b="b"
      :c="c"
      :d="d"
      v-bind="{ x: 100, y: 200 }"
      :updateA="updateA"
    />
  </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);
  let c = ref(3);
  let d = ref(4);
  function updateA(value: number) {
    a.value += value;
  }
</script>

子组件

<template>
  <div class="child">
    <h3>子组件</h3>
    <div>a:</div>
    <div>未接收:</div>
    <GrandChild v-bind="$attrs" />
  </div>
</template>
<script setup lang="ts" name="Child">
  import GrandChild from "./GrandChild.vue";
  defineProps(["a"]);
</script>

孙组件

<template>
  <div class="grand-child">
    <h3>孙子组件</h3>
    <div>b: ~~ c: ~~ d: ~~ x: ~~ y:</div>
    <button @click="updateA(6)">点我:a+6</button>
  </div>
</template>
<script setup lang="ts" name="GrandChild">
  defineProps(["b", "c", "d", "x", "y", "updateA"]);
</script>

# refsrefs、parent

  • $refs:父 ➡︎ 子
  • $parent:子 ➡︎ 父

原理:

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

父组件

<template>
  <div class="father">
    <h3>父组件</h3>
    <div>父组件的值:</div>
    <button @click="changeText1">修改子组件1的值</button>
    <button @click="changeText2">修改子组件2的值</button>
    <button @click="getAllChild($refs)">获取所有子组件实例对象并修改</button>
    <Child1 ref="child1" />
    <Child2 ref="child2" />
  </div>
</template>
<script setup lang="ts" name="Father">
  import Child1 from "./Child1.vue";
  import Child2 from "./Child2.vue";
  import { ref } from "vue";
  let msg = ref("我是父");
  let child1 = ref();
  let child2 = ref();
  function changeText1() {
    child1.value.text = "父组件修改成功";
  }
  function changeText2() {
    child2.value.text = "父组件修改成功";
  }
  // refs: { [key: string]: any }
  function getAllChild(refs: any) {
    for (let key in refs) {
      refs[key].text = "父组件修改成功";
    }
  }
  // 向外部提供数据
  defineExpose({ msg });
</script>

子组件 1

<template>
  <div class="child1">
    <h3>子组件1</h3>
    <div>子组件的值:</div>
    <button @click="changeMsg($parent)">修改父组件的值</button>
  </div>
</template>
<script setup lang="ts" name="Child1">
  import { ref } from "vue";
  let text = ref("我是子1");
  function changeMsg(parent: any) {
    parent.msg = "子组件1修改成功";
  }
  // 向外部提供数据
  defineExpose({ text });
</script>

子组件 2

<template>
  <div class="child2">
    <h3>子组件2</h3>
    <div>子组件的值:</div>
  </div>
</template>
<script setup lang="ts" name="Child2">
  import { ref } from "vue";
  let text = ref("我是子2");
  defineExpose({ text });
</script>

# provide、inject

实现祖孙组件直接通信

具体使用:

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

⚠️ 注:子组件中不用编写任何东西,是不受到任何打扰的

示例

父组件:使用 provide 提供数据

<template>
  <div class="father">
    <h3>父组件</h3>
    <div>父组件的值:</div>
    <div>我是, 今年</div>
    <Child />
  </div>
</template>
<script setup lang="ts" name="Father">
  import Child from "./Child.vue";
  import { ref, reactive, provide } from "vue";
  let msg = ref("我是父组件");
  let person = reactive({
    name: "daidai",
    age: 18
  });
  function updateMsg(value: string) {
    msg.value += value;
  }
  // 向后代提供数据
  provide("msgContext", { msg, updateMsg });
  provide("person", person);
</script>

孙组件:使用 inject 接受数据

<template>
  <div class="grand-child">
    <h3>孙子组件</h3>
    <button @click="updateMsg('~')">修改父组件</button>
    <div>接收:</div>
    <div>我是, 今年</div>
  </div>
</template>
<script setup lang="ts" name="GrandChild">
  import { inject } from "vue";
  let { msg, updateMsg } = inject("msgContext", {
    msg: "默认值",
    updateMsg: (params: string) => {}
  });
  let person = inject("person", { name: "默认", age: 0 });
</script>

# pinia

参考 pinia 部分的讲解

# slot

# 默认插槽

img

示例

子组件

<template>
  <div class="category">
    <h2>组件通信</h2>
    <slot>默认内容</slot>
  </div>
</template>
<script setup lang="ts" name="Category">
  defineProps(["title"]);
</script>

父组件

<template>
  <div class="father">
    <h3>父组件 - 默认插槽</h3>
    <div class="main">
      <Category title="热门游戏列表">
        <ul>
          <li v-for="item in games" :key="item.id"></li>
        </ul>
      </Category>
      <Category title="今日美食城市">
        <img :src="imgUrl" alt="" />
      </Category>
      <Category title="今日影视推荐">
        <video :src="videoUrl" controls></video>
      </Category>
    </div>
  </div>
</template>
<script setup lang="ts" name="Father">
  import Category from "./Category.vue";
  import { ref, reactive } from "vue";
  let games = reactive([
    { id: 1, name: "蛋仔派对" },
    { id: 2, name: "开心消消乐" },
    { id: 3, name: "王者荣耀" },
    { id: 4, name: "英雄联盟" }
  ]);
  let imgUrl = ref("https://z1.ax1x.com/2023/11/19/piNxLo4.jpg");
  let videoUrl = ref("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
</script>

# 具名插槽

示例

子组件

<template>
  <div class="category">
    <slot name="title">默认内容1</slot>
    <slot name="content">默认内容2</slot>
  </div>
</template>

父组件

<template>
  <div class="father">
    <h3>父组件 - 具名插槽</h3>
    <div class="main">
      <Category>
        <template v-slot:content>
          <ul>
            <li v-for="item in games" :key="item.id"></li>
          </ul>
        </template>
        <template v-slot:title>
          <h2>热门游戏列表</h2>
        </template>
      </Category>
      <Category>
        <template v-slot:content>
          <img :src="imgUrl" alt="" />
        </template>
        <template v-slot:title>
          <h2>今日美食城市</h2>
        </template>
      </Category>
      <Category>
        <template #content>
          <video video :src="videoUrl" controls></video>
        </template>
        <template #title>
          <h2>今日影视推荐</h2>
        </template>
      </Category>
    </div>
  </div>
</template>
<script setup lang="ts" name="Father">
  import Category from "./Category.vue";
  import { ref, reactive } from "vue";
  let games = reactive([
    { id: 1, name: "蛋仔派对" },
    { id: 2, name: "开心消消乐" },
    { id: 3, name: "王者荣耀" },
    { id: 4, name: "英雄联盟" }
  ]);
  let imgUrl = ref("https://z1.ax1x.com/2023/11/19/piNxLo4.jpg");
  let videoUrl = ref("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4");
</script>

# 作用域插槽

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(新闻数据在 News 组件中,但使用数据所遍历出来的结构由 App 组件决定)

示例

子组件

<template>
  <div class="game">
    <h2>游戏列表</h2>
    <slot :youxi="games" x="哈哈" y="你好"></slot>
  </div>
</template>
<script setup lang="ts" name="Game">
  import { reactive } from "vue";
  let games = reactive([
    { id: 1, name: "蛋仔派对" },
    { id: 2, name: "开心消消乐" },
    { id: 3, name: "王者荣耀" },
    { id: 4, name: "英雄联盟" }
  ]);
</script>

父组件

<template>
  <div class="father">
    <h3>父组件 - 作用域插槽</h3>
    <div class="main">
      <Game>
        <!-- params 所有传值打包成对象传过来 -->
        <template v-slot="params">
          <ul>
            <li v-for="item in params.youxi" :key="item.id"></li>
          </ul>
          <div></div>
          <div></div>
        </template>
      </Game>
      <Game>
        <template v-slot="params">
          <ol>
            <li v-for="item in params.youxi" :key="item.id"></li>
          </ol>
        </template>
      </Game>
      <Game>
        <template #default="{ youxi, x, y }">
          <h3 v-for="item in youxi" :key="item.id"></h3>
          <div></div>
          <div></div>
        </template>
      </Game>
    </div>
  </div>
</template>
<script setup lang="ts" name="Father">
  import Game from "./Game.vue";
</script>
更新于

请我喝奶茶⌯oᴗo⌯

呆鸭 微信支付

微信支付

呆鸭 支付宝

支付宝