Vue知识点总结(二)选项式api
该系列文章主要基于官方Vue教程,对Vue使用及特性等方面做一个较为系统的总结
官方文档链接Vue.js
选项式api简述
Vue
是一门面向数据的框架,这点在选项式api
中尤为明显
选项式api
是Vue2
中定义组件的一种方法,它采用对象语法,将组件的各项功能划分为不同的选项,如data
,computed
,methods
等,使开发者用配置对象来定义组件的状态,行为和属性
选项式的优点:直观易懂,数据、方法等选项清晰分离,易于维护和管理
选项式的缺点:随着组件复杂度的增加,选项式api会逐步变得难以维护
选项式api配置项
配置项是选项式api的核心,通过将配置项注入组件实例,数据才能在组件中被顺利执行
el
el
配置项用来指定Vue
实例要挂载的DOM
元素,可以使用$el
实例成员访问挂载的根元素
一般在局部创建Vue
实例的时候使用,效果等同于使用mount()
绑定根元素
mount()
用于在Vue3
中createApp()
创建的Vue
实例挂载,在Vue2
中使用$mount()
进行挂载,$mount()
在Vue3
中已被废弃
1 | el:'#app' |
data()
在Vue
中使用使用data()
配置项创建成员属性,data()
本身是个函数,需要return
返回一个普通js
对象
data
中的数据本身应使用$data
实例成员访问属性,但由于配置项中默认给了一个代理,因此可以直接访问具有响应式的数据
1 | export default { |
computed
computed
配置项用来保存一些需要依赖其他属性做响应式修改的属性computed
创建属性的方法和函数类似,只不过可以在属性中使用get()
和set()
自定义修改方式
computed
vsmethods
:由computed
配置项定义的属性只有在原始数据进行修改时才会被重复调用,这在面对一些需要重复使用的数据时能够极大地节约性能
1 | computed: { |
methods
methods
用于创建vue
实例上的事件处理函数,使用方式和computed
类似,使用v-on
事件调用- 在
methods
中使用this
指针访问实例属性,使用$event
示例成员调用事件自身,使用默认的event
参数访问实例方法,event.target
则表示选中的dom
元素本身
1 | data() { |
生命周期钩子
生命周期钩子是一系列钩子函数,用于处理在页面创建不同执行阶段的相关操作
点击查看生命周期执行图示
beforeCreate
:在组件实例初始化完成后调用,只解析了props
传入的数据和setup()
钩子created
:处理完数据部分,如data
、computed
、methods
、watch
,但为挂载,因此还不可调用$el
beforeMount
:组件完成响应式的设置,但还没有创建DOM
节点mounted
:组件被挂载后调用beforeUpdata
:在DOM
节点更新之前调用updated
:任何DOM
节点更新后调用,可使用nextTick()
记录首次更新的结果beforeUnmount
:组件被卸载前调用,调用时组件还具有所有功能unmounted
:组件实例被卸载后调用,可在这个钩子中清理如计时器或事件监听器造成的影响
1 | export default { |
watch侦听器
watch
侦听器用于监听数据变化,当监听的数据变化时,执行一个特定的函数watch
支持简化写法,即写一个监听属性的同名方法,方法中默认传入old
和new
两个参数用来描述数据变化前后的两个状态watch
完整写法需要提供一个同名对象,其中可添加一些配置项执行格外操作- 需要侦听嵌套对象内的属性时,简写写法
["obj.a"](new,old)
,完整写法"obj.a:{}"
- 可使用
this.$watch(value,(new,old)=>{})
创建一个即时监听器,参数配置和定时器类型
handler(new,old)
:完整写法中的触发函数deep
:表示深度监听,如果属性嵌套很深,则使用深度监听会损耗性能immediate
:表示侦听器创建时立即执行1次函数once
:表示变化只执行一次
1 | export default { |
mixins混入
mixins
用于在代码中抽离一些公共部分,来实现组件间的共享逻辑,从而优化工程结构- 需要一个外部组件导出一个需要混合的公共项,在内部组件中调用
mixins
配置项,传递的部分需要一个对象,可以直接传递对象,或者使用函数return
一个对象(这种方式可以传递指定参数)
mixins
覆盖顺序取决于配置项在实例中的位置,Vue3
中虽然保留了mixins
的调用,但由于Vue3
中可以手动抽离逻辑体,故在Vue3
中这个配置项已经相当少见
Mixin文件
1 | // myMixin.js |
调取文件
1 | <!-- ComponentA.vue --> |
选项式api的组件化与参数传递
在vue
中是以一个个组件构成的整个项目,组件以其功能的区别进行划分,而不同功能之间往往需要进行数据交互,因此组件间的参数传递就必不可少,这节主要描述组件间参数传递的多种方式
组件注册
- 一个
Vue
组件在使用前必须先被“注册”,这样Vue
才能在渲染时找到对应实现 - 在选项式
api
中组件注册需要使用components
配置项,分为局部注册和全局注册两种 - 全局注册支持链式注册,但全局注册后,没被使用的组件不会被
tree shinking
组件建议使用大驼峰命名法,也可以使用短横线命名法,两者会互相转化
使用name
配置项可以为组件自身命名,组件自身可以用名字调用自身
全局注册
1 | import MyComponent from './App.vue' |
局部注册
1 | <script> |
Props配置项
props
是组件间通信的最常见的方式,使用时需要在子组件中显示声明它所需要接收的props
,在父组件中以参数的方式进行传递props
显示声明参数时可以使用字符串数组或对象的形式,并且可以配置校检参数对传递的数据进行校检- 建议使用小驼峰命名法,小驼峰和短横线命名法会相互转化
参数传递支持动态绑定与直接传递,一般动态绑定传递会比较常见
props使用单向数据流进行数据传递,因此需要避免在子组件中修改传递的数据的情况
type
:表示确定数据类型required
:指定数据是否必须default
:为数据指定默认值validator
:自定义函数校验,具有默认参数value
,以return
结果判断校验是否通过
1 | // 简写 |
$emit事件
通过上一节props
的介绍中,我们了解props
配置项符合单向数据流,那么如果子组件要传递信息给父组件要怎么做呢,$emit
正是为解决子向父传递数据的问题
- 子组件使用
$emit
抛出事件,父组件中使用v-on
监听事件触发对应函数 - 抛出事件第一个参数为事件名(必填),第二个参数可以设置传递的参数
- 组件内部可以声明
emits
配置项来显示声明将要抛出的事件,配置项指出数组字符串或对象语法,在完整的对象语法中可以进行事件校验
在html
模板中可直接使用$emit
抛出事件,在选项方法中需要使用this.$emit
在参数中可传入函数,这个函数可以当作回调函数触发
1 | // 模板中使用 |
配置项写法示例
1 | // 简化写法 |
组件间的v-model
在上两小节中我们了解了从父组件向子组件单向传递的props
以及由子组件向父组件单向抛出的$emit
事件,那么有没有将数据双向绑定的方式呢?有,答案就是v-model
v-model
的本质是在父组件中动态绑定子组件的某个传递参数,这意味着在子组件中仍需定义props
配置项和$emit
事件处理函数来接收和发送数据,v-model
只是在父组件中对两者合并的简写
在组合式api
中引入了defineModel
宏,可以在子组件中也简化写法
在基础认识篇我们了解了v-model
对表单元素的双向绑定,这种绑定同时也可以使用在组件上,而两者拆解后格式也差不多
v-model
是一个语法糖,是对传递数据以及修改后动态改变数据的合并v-model
绑定的数据也支持修饰符
首先,回顾下原生元素中v-model
的用法1
2
3
4
5
6<input v-model="searchText" />
// 等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
在组件中的拆解1
2
3
4<CustomInput
:model-value="searchText"
@update:model-value="newValue => searchText = newValue"
/>:model-value
为动态绑定的props
,update:model-value
是抛出的方法名,数据由父组件中的searchText
动态控制
其中,model-value
只是绑定的参数名,可以修改为任意值,比如v-model:first-name="first"
则表示双向绑定first-name
,用次方式可以同时绑定多个值
当然,在组件中修改相应的抛出事件可以修改绑定规则,但一般不建议这么做
1 | <UserName |
slot插槽
在调用组件时,我们常常能发现一个问题,即组件中的所有内容都是写死的,如果我仅仅需要修改组件的一部分将变得相当困难,于是,便有了插槽slot
,插槽的作用是方便我们自定义组件内部内容,从而增加组件的可延展性
- 插槽分为基本插槽、具名插槽、作用域插槽
- 插槽中可填入默认内容,插槽添加name参数变为具名插槽,嵌入时,没有用
<template>
指定名称则进入默认的default
插槽中 $slots
实例成员中存放所有定义的插槽
在父组件中使用插槽时,无法访问子组件的作用域,为此可以使用作用域插槽进行数据传递
基本插槽:在子组件中使用
<slot></slot>
指定插槽位置,在父组件中嵌入html
语句以填充基本插槽语法
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// 子组件
<template>
<div>
<h1>我是子组件</h1>
<slot></slot> <!-- 这里是插槽 -->
</div>
</template>
<script>
export default {
name: 'MyComponent'
};
</script>
// 父组件
<template>
<div>
<MyComponent>
<p>这段文本将被插入到子组件的插槽中</p>
</MyComponent>
</div>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent
}
};
</script>具名插槽:用于区分多个插槽而诞生,用
v-slot:
绑定名称,简写为#
,没有绑定则默认绑定default
插槽具名插槽语法
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// 子组件
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
// 父组件
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>- 条件插槽:
$slots
示例成员中存放所有定义的插槽,可根据$slots
配合v-if
来判断插槽存在与否并追加渲染内容条件插槽语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div v-if="$slots.default" class="card-content">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template> - 动态插槽:可以绑定动态参数以动态渲染插槽
动态插槽语法
1
2
3
4
5
6
7
8
9
10<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout> - 作用域插槽:用来解决子组件向父组件传递数据的问题,在子组件中像
props
一样传递数据,在父组件中就可以通过一个参数接收数据作用域插槽语法
1
2
3
4
5
6
7
8// 子组件
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
// 父组件
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
跨层级消息传递
在上文我们了解了父子组件传递数据时会使用props
,但是props
只支持父组件向子组件传递数据,如果组件树层次很复杂,那么使用props
的逐级传递会非常麻烦,因此引入了provide
和inject
用于解决数据的深层次传输问题
- 父级组件使用
provide
配置项提供数据,提供一个对象或者使用函数方式返回一个对象 - 后代组件使用
inject
配置项接收数据,使用方式等同于props
,且注入的数据在data()
渲染完之前就会获取 - 可以自定义注入的本地名称或者指定获取依赖的来源
- 在应用层可使用
app.provide(name,value)
提供全局依赖
provide提供的依赖默认不是响应式的,若需将其变成响应式,需要使用computed()api
在vue2
中还有使用事件总线$on
、$bus
事件总线的方式进行跨层级消息传输的,但在vue3
中这种方式已被废弃
provide
提供数据的两种方式
使用provide提供依赖
1 | // 使用对象提供 |
inject
注入的简写和完整写法
使用inject获取依赖
1 | // 简写 |
响应式依赖provide
完整代码
1 | import { computed } from 'vue' |
Attribute透传
透传指的是一种特性,即当在组件上的属性没有被声明为props
或emits
时,这个属性会自动加到组件内部的根节点上,比如写入class
,这个class
找不到对应的props
用于接收,那它就会在组件内部加上这个class
类,v-on
的事件也是同理
$attrs
实例对象用以接收所有透传的属性- 可在组件内部用
v-bind='$attrs'
指定绑定透传的元素 - 如果组件内部有多个根节点,则必须指定绑定透传对象,否则将会报错
1 | // 单根节点绑定 |
ref组件实例获取
ref
是一个特殊的属性,它允许在一个DOM
元素或子组件实例被挂载后直接获取它的引用,$ref
中存储着所有ref
绑定的值,通过ref
调用后,我们可以直接访问组件实例,并执行相应行为,例如在父组件中控制子组件的函数,ref
还有如下特性
ref
绑定v-for
时,相应引用中包含的是一个数组,但值得注意的是ref
并不保证数组顺序相同,故尽量不要使用- 通过子组件的
expose
选项可以显示声明可以被暴露到父组件的方法或属性,由此确保安全性
1 | // 父组件调用ref |
总结
本文以选项式api
的核心配置项为标准详细描述了日常中使用选项式api
会遇到的绝大部分场景,其中主要包括三部分内容,分别是基础选项、组件选项、参数传递选项,其中,基础选项中包括el
、data()
、computed
、methods
、生命周期钩子、侦听器watch
以及混入mixins
,组件选项中包括components
注册组件,组件透传attribute
的特性以及slot
插槽对组件化的完善,另外还有通过ref
进行组件调用,引入了expose
配置项声明暴露的属性。最后的参数传递选项,包括props
和emits
两个最基本的单向数据传递选项,介绍了组件中的v-model
语法糖,此外,还有provide
和inject
两个选项,完善了组件数据传递的场景