- 组件名格式:PascalCase
组件注册
- 全局注册
- 局部注册
全局注册 — 只有多个页面都要用到才使用全局
1 | import { createApp } from 'vue' |
局部注册
- 使用该组件的父组件引入且在components:{}中注册
Props声明 — 组件间通信父传子
- Prop名字的格式:camelCase
用法
- 子组件:在props定义好属性,然后使用
- 父组件:在组件中调用这个属性,传值
组件的props标注类型 — ts
- defineComponent(): 启用类型推导
1
2
3
4
5
6
7
8
9
10
11import { defineComponent } from 'vue'
export default defineComponent({
props: {
name: String,
age: Number
},
mounted() {
this.name // 类型: string | undefined
}
}) - PropType: 多层级时启用工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
type: Object as PropType<Book>,
required: true
},
callback: Function as PropType<(id: number) => void >
// validater 和 default ts版本低于4.7要使用箭头函数
},
mounted() {
this.book.title // string
this.callback?.('123')
}
})
静态prop:
1 | prop="只能传字符串" |
动态prop:
1 | :prop="可传多种类型" |
使用一个对象绑定多个prop:
1 | v-bind="对象数据" |
单向数据流
- 所有props都遵循着单向绑定原则
- props因父组件的更新而变化
- ❌ 子组件更改prop
prop校验
1 | props:{ |
组件事件
触发与监听事件
子组件事件
- 子组件上的要监听事件要调用 $emit 方法,通过传入事件名称来抛出一个事件
- 示例:
1
<button @click="$emit('enlarge-text')">文本</button>
父组件事件
- 通过 v-on 或 @ 来选择性监听子组件上的事件
- 示例:
1
<子组件名 @enlarge-text="表达式" />
事件参数
- 子组件:$emit(事件名,事件参数)
- 父组件:
1
2
3@事件名="(事件参数) => 表达式"
或者
@事件名="事件处理函数", 然后在methods写事件处理函数
显式声明触发事件+标注类型 emits + ts
- 语法:
1
2emits:[] // 字符串数组语法
emits:{} //对象语法,支持校验 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// addBook为事件名,
// bookName为事件内的一个属性且类型为字符串
emits:{
addBook(payload:{bookName: string}) {
return payload.bookNAme.length > 0
}
},
methods: {
onSubmit: {
this.$emit('addBook',{
bookName: '5655'
})
}
}
组件 v-model
- 默认情况下,v-model 在组件上都是使用 modelValue 作为 prop,并以 update:modelValue 作为对应的事件
组件上使用v-model实现双向绑定
- 方法一
- 组件内的表单元素要有以下两行
1
2:value="modelValue"
@input="$emit('update:modelValue'),$event.target.value"
- 组件内的表单元素要有以下两行
- 方法二
- 使用具有 getter 和 setter 的 computed 属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
}
</script>
<template>
<input v-model="value" />
</template>
- 使用具有 getter 和 setter 的 computed 属性
v-model的参数 — 适用于组件有多个 v-model 绑定
- 通过给 v-model 指定一个参数来更改modelValue这个名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 名字从modelValue改为title
父组件: <子组件名 v-model:title="" />
子组件:
<script>
export default {
props: ['title'],
emits: ['update:title']
}
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
处理 v-model 修饰符
- 自定义的修饰符 capitalize
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31父组件: <MyComponent v-model.capitalize="myText" />
子组件:
<script>
export default {
props: {
modelValue: String,
modelModifiers: {
default: () => ({}) // 默认触发事件
}
},
emits: ['update:modelValue'],
methods: {
// 写给修饰符的对应事件
emitValue(e) {
let value = e.target.value
if(this.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
this.$emit('update:modelValue',value)
}
}
}
</script>
<template>
<input
type="text"
:value="modelValue"
@input="emitValue"
/>
</template>
透传 Attributes:传递给一个组件,却没有被组件声明的属性或v-on
1 | <组件名 class="btn"></组件名> // 这个组件的根元素就多了一个btn的类 |
单根节点:有自动 attribute 透传行为
- 禁用Attributes继承:inheritAttrs: false
- 使用 v-bind=”$attrs” 可以让透传的attribute指定到目标元素
多根节点(一个组件多个子组件):没有自动 attribute 透传行为
- 必须使用 v-bind=”$attrs” 让透传的attribute指定到目标元素
插槽slot —在组件中添加 slot标签
- 插槽的内容无法访问到子组件的状态
- v-slot 简写 #
- 具名插槽: 适用一个组件多个插槽
1
<slot name="" /> // 无名=默认插槽
- 父组件
1
2
3
4
5
6<BaseLayout>
// 当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 `<template> `节点都被隐式地视为默认插槽的内容
// 指定插入那个插槽
<template v-slot:插槽名></template>
<template #插槽名></template>
</BaseLayout> - 动态插槽名:
1
<template #[插槽名]></template>
- 作用域插槽
- 插槽内容想要同时使用父组件域内和子组件域内的数据
- 传递prop:
1
<slot name="可不写为默认插槽" message="hello" />
- 接受插槽:
- 默认插槽:
1
<组件名 v-slot="slotProps">{{slotProps.xxx}}</组件名>
- 具名插槽:
1
<组件名 #插槽名="插槽名Props">{{}}</组件名>
- 默认插槽:
依赖注入
Provide
- 为组件后代提供数据
1
2
3
4
5exeport default {
provide: {
message: 'test'
}
}
应用层 Provide
- 为整个应用层提供依赖app页面
1
app.provide('注入名',值)
Inject
- 注入上层组件提供的数据
1
2
3
4
5
6
7
8
9
10
11export default {
inject: ['message'],
created() {
console.log(this.message) // test
}
data() {
return {
fullMessage: this.meaaage // data的时候拿得到this.message
}
}
}
注入别名/默认值(不要求必须有提供者)
1 | inject: { |
和响应式数据配合使用
1 | import { computed } from 'vue' |
异步组件
- 仅在需要时再从服务器加载相关组件 defineAsyncComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32import { defineAsyncComponent } from 'vue'
// 方法一:
const AsyncComp1 = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
// 方法二:
const AsyncComp2 = defineAsyncComponent({
loader: () => import('组件路径'), // 加载组件
loadingComponent: LoadingComponent, // 加载异步组件时使用的组件
delay: 200, // 展示加载组件前的延迟时间,默认为 200ms
errorComponent: ErrorComponent, // 加载失败后展示的组件
timeout: 3000
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
// 全局注册:
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
// 局部注册:
components: {
异步组件名: defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
}
动态组件 — 适用于Tab界面
- 在多个组件间作切换时,被切换掉的组件会被卸载
1
<component :is="组件名/组件对象">
DOM 模板 — 在 DOM 中直接书写 Vue 模板
- 区分大小写,以 kebab-case (短横线连字符) 形式
- 显示关闭标签:
<></>
- 元素位置限制,ul标签、ol标签、tr标签 等要放在特定元素中才会显示
- 解决办法:
1
2
3<table>
<tr is="vue:要放的组件名" />
</table>