【Vue3】保姆级毫无废话的进阶到实战教程 - 03
评论区有个同学让我总结一下 Vue 中的组件通信,虽然我觉得这可能是属于入门和基础必备,不再适合放入进阶系列,但是还是再好好总结一次,当做复习咯 ~
前言
:吐槽一下,Vue 和 React 一个很大的不同就是,Vue 是当爹又当娘,给了你大量的所谓“语法糖
”,生怕你脑子不够用。。。然后就造成一个问题:满屏的语法糖!各种的写法:vue2 写法、Option API 写法
、Composition API 写法
,甚至 JavaScript 和 TypeScript 写法,各不相同,有种让人直呼 “小可爱” 的冲动。React 则是把你当成捡来的,基本”
生活物资
“给你了,其余自给自足,突出一个灵活,但是通常都有最佳实践。。。PS:这里没有好坏之分,各有各的好,不引战 ——
能解决问题的就是好技术!
~
所以,由于本人基本只用 TypeScript,所以本篇只关注
Composition + TypeScript
(主要是和 React Hooks 基本可以无缝切换) 这种写法。
组件通信,无外乎下列三种情况:
- 父组件 ——> 子组件通信
- 子组件 ——> 父组件通信
- 跨组件通信
PS
:本系列只涉及到 Vue 3 中的新内容,所以下面我介绍的都是基于 Vue 3 Composition API 中的实现,没有去总结那些已经不常用或者被废弃的冷知识了,本文中的这些技巧已经足够绝大部分场景使用了。
1. 父 ——> 子
通信
1.1 传递状态:defineProps
当使用 <script setup>
时,defineProps()
宏函数支持从它的参数中推导类型:
<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 的类型通常更直接:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
这被称之为“基于类型的声明
”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。
基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。
我们也可以将 props 的类型移入一个单独的接口中:
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
上一篇文章有讲过,父子传递数据比较简单,就是通过 defineProps
传递数据(静态数据或者动态数据):
简单来个代码示例:
如果你想在父组件传入的值发生更改时调用子组件内部的方法,要怎么做?简单,用 watch:
这样,每次父组件传入值改变时,子组件就会调用 callMe
方法。来试试:
另外,还可以在 defineProps
中做更多的事情:
默认值:withDefaults
当使用基于类型的声明时,我们失去了为 props
声明默认值的能力。这可以通过 withDefaults
编译器宏解决:
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
有一些问题:
-
紧耦合:父组件可以访问子组件的任何内容,即使这些内容是不希望被暴露出去的。如果子组件改变了其内部逻辑,这可能会导致奇怪的副作用或错误。
-
类型不安全,因为
$refs
属性是一个通用对象,没有任何类型信息或代码补全。这就很容易造成错误或访问不存在的内容。
defineExpose
Vue 3 新推出了一个内置的宏:defineExpose()
,一个可以在组件的 setup()
函数中调用的函数。参数是要暴露的内容。是这样用的:
来看看效果:
它带来的好处是:
-
使父组件和子组件之间的连接清晰明确。子组件声明它所暴露的内容,而父组件只能访问所暴露的内容。
-
提高类型安全。暴露的内容是有类型信息或代码补全,从而减少出错的几率,提升了开发体验。
3. 跨组件通信
3.1 mitt
首先可能会想到 eventBus
,先来看看 Vue2 中熟悉的用法:
到 Vue 3 中,$on
、$off
和 $once
方法已从 Vue 实例中完全删除,而且 vue3 中没有Vue构造函数,也就没有 Vue.prototype
以及组合式API写法没有 this
。因此,要使用 eventBus
模式,就需要安装一个外部事件发射器和监听器包,常用的比如 mitt。
在组件中使用:
<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 类型的值:
<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>
取值:
<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>
来看一个例子:
效果:
转载自:https://juejin.cn/post/7282096012725469218