Vue3 中, 基于 TS 封装组件的注记
封装组件重点在于:
- 如何导出属性, 事件
- 如何处理插槽
- 如何控制生命周期
参考官方文档:
- 组件: https://cn.vuejs.org/guide/components/registration.html
- TS 篇: https://cn.vuejs.org/guide/typescript/composition-api.html
基本上就是官方文档的梳理
1. 注册组件的方法
方法包括:
- 全局注册 (在
main.ts
中导入, 然后使用component()
方法给app
注册) - 局部注册 (在具体需要的页面中
import
)
// 全局注册
import <实现> from '...'
const app = createApp({})
app.component('组件名', <实现>)
2. 属性的定义
仅列举个人比较习惯的方案
- 使用
interface
定义组件需要的数据类型 - 使用
defineProps<T>
来定义参数 - 使用
withDefault
来定义默认值
注意属性值不可修改
定义属性数据类型:
<script lang="ts" setup>
interface Person {
name: string;
age: number;
gender: string;
}
</script>
定义属性:
<script lang="ts" setup>
interface Person {
name: string;
age: number;
gender: string;
}
const props = defineProps<Person>()
</script>
对于需要基于属性传入的数据, 而内部又需要进行修改的数据, 可以使用:
const props = defineProps<...>()
const propName = ref<...>(props.propName)
而复杂数据也支持使用计算属性
const props = defineProps<...>()
const propName = computed<...>(() => {
return props.xxx 表达式
})
3. 属性默认值
为属性定义默认值, 使用 withDefaults
宏:
const props = withDefaults(defineProps<类型>, {
属性: 默认值
})
简单说就是将
props
作为参数传入, 设置其默认值
4. 定义事件
- 定义事件声明有三种方式: 运行时声明, 类型声明, 基于字面量声明.
- 基于运行时声明最简单, 只用定义事件名即可
- 基于类型与字面量的声明逻辑一样, 只是形式不同
const emit = defineEmits(['事件名']) // 基于 运行时
// 基于声明
const emit = defineEmits<{
(e: '事件名', 参数列表): void
}>()
const emit = defineEmits<{
'事件名': [...] // 元祖, 或数组, 用于描述参数列表
}>()
5. v-model
本质
v-model
是两个部分构成:
- 一个是属性.
- 一个是
input
事件. 一般约定事件名带有update:
前缀.
6. 透传 Attributes
所谓透传, 是指定义组件时未设置属性, 以及事件. 但在使用组件时传入了数据.
常见的透传属性有: style
, class
, 和 id
.
规则:
- 默认会将该属性绑定到组件的根元素上. 如果根元素是另一个组件, 同样待之.
- 如果根元素非单根元素 (多根节点), 而属性又未被使用, 则会警告.
- 如果属性是
style
, 或class
则会合并. 其他属性则会被传入元素覆盖. - 如果禁用自动透传, 使用
inheritAttrs: false
来配置. - 如果需要在组件内模板中引用传入属性, 使用
$attrs
来引用. 其中props
,emits
定义的成员不包含在其内. - 若要全部绑定到某一子元素, 可以使用:
v-bind="$attrs"
- 如果在组件内 JS 中使用透传属性, 则使用
useAttrs()
方法来获得该属性对象.
// 禁用透传
defineOptions({
inheritAttrs: false
})
使用组件时传入的属性会覆盖定义的属性
7. 插槽
插槽, 用来定义容器组件, 定义时只定义容器, 使用时提供内部组件, 最终组合在一起使用.
说明:
- 插槽使用
<slot></slot>
来定义. 组件中可以有多个<slot></slot>
. 每一个必须有唯一的名字 (name
属性), 不写名字默认为default
. - 使用插槽时, 使用
<template v-slot:名字></template>
来指定使用哪一个插槽接纳元素.v-slot:名字
可简写为#名字
. - 使用插槽时, 未指定插槽名的, 顶级的元素, 均会放置于
default
插槽中. - 作用域插槽, 即在
<slot>
中传入数据, 这些数据就会交给将来放置的子组件中.
组件定义中, 无法访问插槽内的数据 (因为定义时, 会传入什么是不确定的). 反过来,
<slot>
中将来放置的内容可以使用组件内的数据, 即作用域插槽.
7.1 文档配图很直观
默认插槽
定义
<button class="fancy-btn">
<slot></slot> <!-- 插槽出口 -->
</button>
使用
<FancyButton>
Click me! <!-- 插槽内容 -->
</FancyButton>
插槽内容可以是任意的内容.
具名插槽
7.2 动态插槽名
可以将插槽名定义为响应式变量, 来动态控制:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
7.3 作用域插槽
- 组件无法访问到 slot 中的内容.
- 但是 slot 中可以访问组件的内容, 即作用域插槽.
- 作用域插槽在使用中可以利用解构语法, 例如:
v-slot="{ text, count }"