【Vue3】保姆级毫无废话的进阶到实战教程 - 03

评论区有个同学让我总结一下 Vue 中的组件通信,虽然我觉得这可能是属于入门和基础必备,不再适合放入进阶系列,但是还是再好好总结一次,当做复习咯 ~

前言 :吐槽一下,Vue 和 React 一个很大的不同就是,Vue 是当爹又当娘,给了你大量的所谓"语法糖 ",生怕你脑子不够用。。。然后就造成一个问题:满屏的语法糖!各种的写法:vue2 写法、Option API 写法Composition API 写法,甚至 JavaScript 和 TypeScript 写法,各不相同,有种让人直呼 "小可爱" 的冲动。

React 则是把你当成捡来的,基本"生活物资"给你了,其余自给自足,突出一个灵活,但是通常都有最佳实践。。。

PS:这里没有好坏之分,各有各的好,不引战 ------ 能解决问题的就是好技术! ~
所以,由于本人基本只用 TypeScript,所以本篇只关注 Composition + TypeScript(主要是和 React Hooks 基本可以无缝切换) 这种写法。

组件通信,无外乎下列三种情况:

  1. 父组件 ------> 子组件通信
  2. 子组件 ------> 父组件通信
  3. 跨组件通信

PS:本系列只涉及到 Vue 3 中的新内容,所以下面我介绍的都是基于 Vue 3 Composition API 中的实现,没有去总结那些已经不常用或者被废弃的冷知识了,本文中的这些技巧已经足够绝大部分场景使用了。

1. 父 ------> 子 通信

1.1 传递状态:defineProps

当使用 <script setup> 时,defineProps() 宏函数支持从它的参数中推导类型:

ts 复制代码
<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})

props.foo // string
props.bar // number | undefined
</script>

这被称之为"运行时声明 ",因为传递给 defineProps() 的参数会作为运行时的 props 选项使用。

然而,通过泛型参数来定义 props 的类型通常更直接:

ts 复制代码
<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>

这被称之为"基于类型的声明"。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。

基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。

我们也可以将 props 的类型移入一个单独的接口中:

ts 复制代码
<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()
</script>

上一篇文章有讲过,父子传递数据比较简单,就是通过 defineProps 传递数据(静态数据或者动态数据):

简单来个代码示例:

如果你想在父组件传入的值发生更改时调用子组件内部的方法,要怎么做?简单,用 watch:

这样,每次父组件传入值改变时,子组件就会调用 callMe 方法。来试试:

另外,还可以在 defineProps 中做更多的事情:

默认值:withDefaults

当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:

ts 复制代码
export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

这将被编译为等效的运行时 props default 选项。此外,withDefaults 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。

1.2 传递方法:defineEmits

上一篇文章有讲过,使用 defineEmits

测试:

2. 子组件 ------> 父组件通信

熟悉 Vue 2 的同学应该知道,在 Vue 2 中的 $refs 属性也可以访问子组件的内容。例如:

但是,使用 $refs 有一些问题:

  1. 紧耦合:父组件可以访问子组件的任何内容,即使这些内容是不希望被暴露出去的。如果子组件改变了其内部逻辑,这可能会导致奇怪的副作用或错误。

  2. 类型不安全,因为 $refs 属性是一个通用对象,没有任何类型信息或代码补全。这就很容易造成错误或访问不存在的内容。

defineExpose

Vue 3 新推出了一个内置的宏:defineExpose(),一个可以在组件的 setup() 函数中调用的函数。参数是要暴露的内容。是这样用的:

来看看效果:

它带来的好处是:

  1. 使父组件和子组件之间的连接清晰明确。子组件声明它所暴露的内容,而父组件只能访问所暴露的内容。

  2. 提高类型安全。暴露的内容是有类型信息或代码补全,从而减少出错的几率,提升了开发体验。

3. 跨组件通信

3.1 mitt

首先可能会想到 eventBus,先来看看 Vue2 中熟悉的用法:

到 Vue 3 中,$on$off$once 方法已从 Vue 实例中完全删除,而且 vue3 中没有Vue构造函数,也就没有 Vue.prototype 以及组合式API写法没有 this。因此,要使用 eventBus 模式,就需要安装一个外部事件发射器和监听器包,常用的比如 mitt

在组件中使用:

ts 复制代码
<script setup lang="ts">
// import ParentComponent from "./components/ParentComponent.vue";
import ComponentA from "./components/ComponentA.vue";
import ComponentB from "./components/ComponentB.vue";
</script>

<template>
  <!-- <ParentComponent /> -->
  <ComponentA />
  <ComponentB />
</template>

<style scoped></style>

试试:

3.2 依赖注入:provide + inject

类似于 React 中的 Context,只是用起来比 React 稍微简单点而已,主要用在跨层级组件通信传值~

  • provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

  • 当使用 TypeScript 时,key 可以是一个被类型断言为 InjectionKey 的 symbol。InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide()inject() 之间值的类型。

与注册生命周期钩子的 API 类似,provide() 必须在组件的 setup() 阶段同步调用。

还可以传 Symbol 类型的值:

ts 复制代码
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 提供静态值
provide('foo', 'bar')

// 提供响应式的值
const count = ref(0)
provide('count', count)

// 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script>

取值:

ts 复制代码
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'

// 注入不含默认值的静态值
const foo = inject('foo')

// 注入响应式的值
const count = inject('count')

// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)

// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')

// 注入一个值,若为空则使用提供的函数类型的默认值
const fn = inject('function', () => {})

// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('factory', () => new ExpensiveObject(), true)
</script>

来看一个例子:

效果:

相关推荐
郑州光合科技余经理10 分钟前
本地生活系统:多业务订单字段怎么分账本导出
java·开发语言·前端·数据库·uni-app·php·ai编程
怕浪猫7 小时前
FDE 如何正确解决现场项目工程性的问题
前端·后端·ai编程
西瓜太郎12347 小时前
外层模型卡片与分组详情成功率不一致,应该怎么排查?
前端·typescript·go·软件工程·react
GreenTea7 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
毕业设计70310 小时前
(免费领源码) 基于微信小程序的预制菜商城的设计与实现25172-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·python·mysql·微信小程序·pycharm·微信开发者工具·推荐算法
xifangge202510 小时前
AGENTS.md 怎么写?涵盖 Java、Python、Vue、Go 的 8 套开箱即用模板
java·vue.js·python
几何心凉10 小时前
没有万能模型:我用 AiiOnly Token Plan,把多款大模型的长处拼进同一个项目
前端
kyriewen10 小时前
Claude Code 的额度今天缩水了17%——官方公告上写的是"永久提高25%"
前端·ai编程·claude
雪芽蓝域zzs11 小时前
vue解构平铺VS对象包裹
前端·javascript·vue.js
风骏时光牛马12 小时前
从零到实战,完整AI学习路线
前端