这篇文章是个人学习时记录的要点汇总,因为省略了代码示例,更没有覆盖完整知识面,所以仅用作学习过后的复习材料,系统学习应参考Vue.js官方文档(本文的几个引用段落均摘自该文档)
本文知识点以Vue2为基准,并补充了Vue3的内容作为对照,现有特性的变动会在正文中用荧光笔标注,新加特性会在标题上注明Vue3新,一些整体性变动已在开篇的前置说明中提及,另外,个人认为的易错点及需要特别注意的地方会在标题上注明重点。
目录
- 前置说明
- 1. Vue.js的特点
- 2. MVVM模式
- 3. 模板语法
- 4. 数据代理与劫持(重点)
- 5. 事件处理
- 6. 计算属性
- 7. 监听属性
- 8. this指向问题(重点)
- 9. 绑定样式
- 10. 条件渲染
- 11. 列表渲染(重点)
- 12. 数据监视(重点)
- 13. 过滤器
- 14. 指令
- 15. 生命周期(重点)
- 16. 组件的本质(重点)
- 17. 脚手架
- 18. ref属性
- 19. 组件间props通信
- 20. 非props
- 21. 组件间事件通信
- 22. 插槽
- 23. Provide和Inject
- 24. 动态组件
- 25. 异步组件
- 26. 过渡与动画
- 27. Mixin
- 28. 自定义指令
- 29. Teleport(Vue3新)
- 30. render函数
- 31. 插件
- 32. Composition API(Vue3新)
- 33. setup函数(Vue3新)
- 34. ref/reactive函数(Vue3新 + 重点)
- 35. computed函数(Vue3新)
- 36. watch函数(Vue3新 + 重点)
- 37. watchEffect函数(Vue3新)
- 38. 生命周期函数(Vue3新)
- 39. toRef/toRefs函数(Vue3新)
- 40. provide/inject函数(Vue3新)
- 41. Vuex
- 42. Vue-Router
- 43. 路由守卫
- 44. script setup语法糖(Vue3.2新)
前置说明
在Vue2中,我们习惯通过const vm = new Vue()
的构造函数方式来创建vm实例,并使用el
属性或者vm.$mount()
来挂载,从同一个Vue构造器身上构造的实例会共享同一份Vue配置,当我们创建全局内容时,是通过类似Vue.component
这样的全局API来定义的,这种模式会造成用例间的的配置污染问题。
因此,Vue3改变了模式,不再有组件构造器的概念,在任何情况下我们都应使用const app = Vue.createApp()
这种构造工厂方式来创建独立的app实例,并且使用app.mount()
来挂载。粗略上看,app实例充当着先前vm实例的角色,不过app实例更加轻量,而且它拥有自己的全局配置域,先前定义全局内容的语法都变成了形如app.component
、app.config
、app.directive
这样的形式。
Vue2中渲染策略是将被指定的HTML元素直接替换为template
中的内容,并且每个组件必须只有一个根元素,Vue3中将渲染策略调整为替换指定元素的innerHTML,不再有单个根元素的限制。
此外,Vue2中用于构造Vue实例的data
选项既可以用对象式声明,也可以用函数式声明(返回对象的函数),Vue3已不存在共享配置的vm实例,故应该始终用函数式声明来定义data
。
(由于本文内容是以Vue2为基准的,这些整体性的变动会四处涉及,所以我将这部分变动放在开篇统一说明,后文将不再赘述)
1. Vue.js的特点
- Vue.js是一个用于构建用户界面的JavaScript库,它强调渐进式构建应用,并且遵循MVVM模式,体积小、效率高
- Vue.js既借鉴了Angular的模板特性与数据绑定思想,又参考了React的组件化与虚拟DOM技术
2. MVVM模式
- M(Model)指模型层,用于业务处理及数据存储的纯状态模型,在Vue中可特指
data
对象 - V(View)指视图层,用于与用户交互的图形界面,在Vue中可特指
template
模板 - VM(ViewModel)指视图模型层,供视图层使用的包含状态和行为的数据模型,在Vue中可特指
vm
实例
传统的开发,Model层的数据需要开发者自行操纵DOM去反应到View层上,使用MVVM模式,Model层和View层便完全解耦了,开发者只需要在ViewModel层中声明数据与视图之间的关系,之后一旦Model层的数据发生变化,ViewModel便会自行高效的去更新View层,如此一来,开发者能够将更多精力放在逻辑上,而不用考虑操控DOM的问题。
3. 模板语法
- 插值表达式
{{ exp }}
,exp可为变量或JS表达式,结果会在页面上显示出来 - 单向数据绑定
v-bind:xxx="yyy"
,可以简写成:xxx="yyy"
,数据只能由data流向页面 - 双向数据绑定
v-model:value="yyy"
,可以简写成v-model="yyy"
- 事件监听
v-on:xxx="yyy"
,可以简写成@xxx:="yyy"
4. 数据代理与劫持(重点)
- 数据代理指通过一个代理对象实现对源对象相关属性的操作
- Vue通过vm对象来代理data对象中属性,这样能够更加方便的操作data中的数据
- 数据劫持指当源对象相应的属性被读取或修改时,将被会拦截下来并执行代理对象预先设置好的逻辑,Vue就是通过数据劫持实现了响应式编程
- Vue2响应式原理:通过Object.defineProperty()把data对象中所有属性添加到vm实例上,然后为每一个添加到vm实例上的属性都指定一个getter/setter,最后在getter/setter内部去操作data中对应的属性(getter会感知数据的读取,setter会感知数据的修改)
- Vue3更换了响应式的实现方式,通过ECMAScript 6提供的Proxy对象来实现数据代理与劫持,Proxy对象包含getter/setter/deleteProperty,内部又通过Reflect对象提供的方法去操作源对象身上的属性,Proxy对象不仅能感知基本的数据变动,还能够检测到先前Vue2无法感知到的变化
- 关于Vue感知数据变化的说明,以及Vue2响应式的缺陷,请移步第12条数据监视
5. 事件处理
- 事件的回调配置在
methods
中,Vue最终会将其添加到vm实例上 - 事件修饰符写法形如
@click.xxx
,常见有.prevent
阻止默认事件,.stop
阻止事件冒泡,.once
使事件只触发一次 - 按键修饰符写法形如
@keyup.xxx
,常见的有.enter
回车,.tab
制表,.space
空格等等,也可以直接书写按键码,例如@keyup.13
- Vue3不再支持键码作为修饰符
6. 计算属性
- 在
computed
对象中定义计算属性相关的函数,Vue最终会将该属性添加到vm实例上 - 计算属性需要使用set和get方法定义操作,当只需要get方法时可以将对象简写成单个函数(形式参照get方法)
- 计算属性与在
methods
中定义的方法不同,计算属性的方法只会在依赖的数据发生变化时重新执行,而普通方法会在页面重新渲染时就会执行 - 因为计算属性有缓存机制,所以当
methods
和computed
都能实现同一个功能时,推荐使用computed
以减少不必要的运算
7. 监听属性
- 在
watch
属性对象中定义监听属性的方法,属性名应与被监听的属性一致 - 使用
handler(newValue,oldValue)
方法操作逻辑 - 当被监视的属性变化时, 回调函数自动调用
- 可配置
immediate:true
使得页面渲染时即执行一次逻辑 - 可配置
deep:true
开启深度监视(能够监听到对象内部值的变化) - 当不需要配置多余参数时,可以将监听对象简写成单个函数(形式参照handler方法)
computed
能够实现的功能watch
都能够实现,除此之外watch
能够实现异步操作
8. this指向问题(重点)
- 所有被Vue直接管理的函数(例如
methods
、computed
、watch
中定义的函数),尽量使用普通函数定义,因为普通函数的this会被自动指向vm实例或组件实例,而箭头函数的this会指向Window - 所有不被Vue直接管理的函数(例如定时器的回调函数、Ajax的回调函数),尽量使用箭头函数定义,这样this会自动向上匹配到vm实例或组件实例
9. 绑定样式
:class
可绑定class样式,可书写字符串、数组、对象:style
可绑定style样式,可书写数组或对象
10. 条件渲染
v-if
控制DOM元素的加载与卸载,当其值为假时将该元素从DOM结构中移除v-if
可与v-else-if
和v-else
一起使用,但需要确保结构的连续性v-show
控制DOM元素的显示与隐藏,当其值为假时会使该元素display:none
v-if
适合切换频率较低的场景,v-show
适合切换频率较高的场景
11. 列表渲染(重点)
- 语法形式为
v-for="(item,index) in xxx" :key="yyy"
- key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue会将新虚拟DOM与旧虚拟DOM进行比较
- 若旧虚拟DOM中存在对应的
key
的元素,且内容没有发生变化,则复用 - 若旧虚拟DOM中存在对应的
key
的元素,且内容发生变化,则用新的元素替换旧的元素 - 若旧虚拟DOM中不存在对应的
key
的元素,则创建新的元素 - 虚拟DOM是Vue构建出来的一个真实DOM的副本,它便于Vue做新旧差异比对,关于虚拟DOM的进一步说明请跳转至第30条render函数
- 当存在逆序操作时,应避免使用
index
作为key
,因为逆序操作会破坏原列表的顺序,这将导致Vue又重新渲染了某些未变化的节点,降低效率,其次,若结构中包含输入框,会造成数据错位
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
12. 数据监视(重点)
- Vue会通过setter监视
data
中所有层次的数据变化,setter中被包含着触发视图更新的逻辑 - 由于JavaScript的限制,Vue不能检测到数组和对象的某些变化
- 若在Vue实例创建后再向对象中追加属性,Vue将无法做出监视,但可使用
Vue.set(target,name,value)
的方式追加响应式属性(不得向vm实例或根数据对象追加属性) - 若利用索引直接设置一个数组项,或者修改数组长度时,Vue都无法感知变化,应尽可能使用数组操作类函数,例如
push()
、shift()
等(它们已被Vue接管,每次调用后会触发一次视图更新),或者使用Vue.set(target,name,value)
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。
- Vue3已经能够监视到上述对象和数组的相关变化了
13. 过滤器
- 当需要对待显示的数据进行简单的格式化处理时,可使用过滤器
filters
- 使用过滤器语法:
{{xxx|过滤器名}}
- 过滤器并没有改变原本的数据,而是产生新的数据
- Vue3移除了过滤器,请使用计算属性等现有策略来实现相同的功能
虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。
14. 指令
已提及过的指令如v-on、v-bind、v-model、v-if、v-show等略
指令名 | 作用 |
---|---|
v-text | 更新元素的textContent |
v-html | 更新元素的innerHTML(可引起XSS攻击,慎用) |
v-cloak | 无值属性,Vue接管容器后会删除该属性,可配合display: none 解决页面加载时的闪现问题 |
v-once | 使元素只被Vue渲染一次 |
v-pre | 跳过该元素的编译过程 |
- 使用
directives
可配置局部自定义指令 - 使用
Vue.directive(name,obj)
可配置全局自定义指令
15. 生命周期(重点)
- 流程:new Vue() => beforeCreate => 数据代理与劫持 => created => 判断el/template编译模式 => beforeMount => 虚拟DOM转换为真实DOM => mounted => 当监视的数据发生变化 => beforeUpdate => 新旧DOM比对 => updated => 当触发销毁操作时 => beforeDestroy => destroyed
- 生命周期函数的this指向vm实例或组件实例
mounted
阶段时编译已完成,常在mounted
中发送Ajax请求、启动定时器、绑定自定义事件、订阅消息等beforeDestory
阶段时各个配置项还处于最后的可用期限,常在beforeDestroy
中清除定时器、解绑自定义事件、取消订阅消息等- 在
beforeDestroy
中操作数据是没有意义的,因为该阶段Vue已不会触发视图更新 - 销毁Vue实例后,原生DOM事件不会失效
- 生命周期函数也称为生命周期钩子(将特定的逻辑像钩子一样挂在程序执行的特定阶段 )
- Vue3只有在app.mount调用后才会进入beforeCreate阶段
- Vue3将beforeDestroy生命周期函数被重命名为beforeUnmount,将destroyed生命周期函数重命名为unmounted
16. 组件的本质(重点)
- 组件是实现页面局部功能的代码和资源的集合
- Vue中使用组件需要先定义和注册
- 使用
Vue.extend(options)
定义组件,options对象与创建Vue实例时的参数类似,但不得指定el
(容器应由vm实例决定),并且data
配置项只能使用返回对象的函数式(避免组件复用时存在数据的引用关系) - 使用
components
可以注册局部组件,使用Vue.component(name,component)
可以注册全局组件 - 使用
<Xxx></Xxx>
或<Xxx/>
均可使用组件,但务必在脚手架环境下使用 - 定义组件时可以省略
Vue.extend
,在注册组件时Vue会自动识别并调用 - 每个组件本质上都是一个独立的
VueComponent
构造函数,它是由Vue.extend
生成的 - 当Vue解析到某个组件调用时,会自动创建该组件对应的
VueComponent
实例 - 组件中
data
、methods
等函数的this指向该组件实例 VueComponent
的原型对象的隐式原型属性指向Vue的原型对象,即VueComponent.prototype.__proto__===Vue.prototype
,这样可以通过组件实例访问Vue原型上的方法- Vue3摒弃了组件构造器的概念,不再存在Vue.extend,应该始终使用 createApp来挂载组件
- 由于Vue3用app实例取代了vm实例,每个app实例有独立的全局配置,故Vue.prototype被替换为app.config.globalProperties
17. 脚手架
- Vue CLI是官方推荐的脚手架工具,它能够一键搭建Vue开发所需的环境,集成了Babel、ESLint等插件
vue.js
是完整版Vue,包含核心功能和模板解析器,而vue.runtime.js
只包含核心功能,所以创建Vue实例时不得使用template
配置项,需要使用render
函数(关于render函数的说明请跳转至第30条)- 可在项目根目录创建
vue.config.js
来修改脚手架的默认参数,常用的配置如pages
设置入口文件、lintOnSave
设置语法检查 scoped
可以让样式在组件的局部生效,防止冲突。它的原理是给元素生成随机data-
标签属性,在应用样式时使用标签属性选择器lang
可以指定样式格式,如css/less等,隐式指定为css,语法形如<style lang="less" scoped>
18. ref属性
ref
属性被用来给元素或子组件注册引用信息- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象
- 通过
this.$refs.xxx
获取
19. 组件间props通信
props
能让子组件能接收父组件传递过来的数据- 父组件传递数据语法
<Demo name="xxx"/>
- 子组件接收数据语法
props:['name']
props
中的数据是只读的(单向数据流),如果想要修改需要将其拷贝至data
中,再修改data
中的备份props
传过来的若是对象类型的值,修改对象中的属性时不会报错,但不推荐这样做
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。
- 接收数据时可以限制数据类型,例如
props:{name:String}
- 接收数据时可以同时限制类型、限制必要性或指定默认值
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
20. 非props
- 当父组件利用属性向子组件传值,但子组件没有定义
props
配置项去接收时,该属性会被作为非prop属性处理 - 非prop属性会被直接作为子组件HTML根节点的标签属性加载
- 在子组件中定义
inheritAttrs: false
可以禁用非props功能(该配置不会影响class和style的绑定) $attrs
可以获取所有非prop属性值组成的对象(但不包含class和style属性)- Vue3中的$attrs现在包含了class和style属性,inheritAttrs: false也可以禁用class和style的绑定了
21. 组件间事件通信
- A是父组件,B是子组件,若B想给A传数据,那么就要在A中给B绑定自定义事件
- 第一种方式
<Demo @xxx="test"/>
- 第二种方式
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('xxx',this.test)
}
- 触发自定义事件
this.$emit('xxx',数据)
- 解绑自定义事件
this.$off('xxx')
- 使用
once
修饰符可以让事件只调用一次,要调用原生DOM事件可使用native
修饰符 - 可以在构建vm实例的
beforeCreate
阶段将vm实例注册到Vue的原型对象上,形如Vue.prototype.$bus = this
,这样就相当于安装了一个全局事件总线,可以实现任意组件间通信 - Vue3已移除了$on及$off,第二种绑定方式已不可用,全局事件总线需使用第三方库如mitt来实现
- Vue3要求绑定的事件需要在子组件的emits选项中显式声明(非必须,不遵守时仅会报警告)
22. 插槽
- 插槽可以让父组件将指定的内容集合传递给子组件,内容可以包含任何模板代码和HTML标签,甚至其它组件
- 子组件中书写
<slot></slot>
以作为内容集合的出口 - 插槽定义的内容集合只能访问父组件自身的data,无法访问子组件的data
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
- 在
<slot></slot>
中可以包裹一些内容作为备用,当插槽为空时备用内容将被渲染出来 - 父组件在定义插槽内容时,可以包裹一层
<template v-slot:xxx>...</template>
以便定义一个带有名字的独立插槽(具名插槽),子组件用<slot name="xxx"></slot>
作为具名插槽的出口 - 实际上,普通的插槽也是一个具名插槽,它的名字为default,
<slot></slot>
的完整写法是<slot name="default"></slot>
<template v-slot:xxx>...</template>
可以缩写成<template #xxx>...</template>
- 默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确,只要出现多个插槽,请始终为所有的插槽使用完整的基于
<template>
的语法 - 如果想让父模板内容访问到子组件中的data项,可以让子组件给父组件传参,形如
<slot :yyy="yyy"></slot>
,父组件形如<template v-slot:xxx="slotProps">{{slotProps.yyy}}</template>
即可接收(slotProps也不是固定的名字,可以随意替换)
23. Provide和Inject
- 假设父组件的子组件也有它的子组件,如此嵌套下去会形成一个很深的组件链,此时子孙组件想获取祖先组件的数据,若使用props方式传参,中间每一层都被迫向下递交数据,会显得非常累赘
- 祖先组件使用
provide: {xx:yy}
可以向所有子孙组件传递数据值,子孙组件使用inject: ["xx"]
可以直接接收对应的值,不再需要经过中间的组件 - 如果祖先组件想将自身data中的值通过
provide
传递给子孙们,必须写成返回对象的函数,形如provide() { return {xx: this.yy} }
- 默认情况下,Provide/Inject绑定并不是响应式的,祖先的数据发生变化并不会引起子孙相应的变化
实际上,你可以将依赖注入看作是“长距离的 prop”,除了:
父组件不需要知道哪些子组件使用了它 provide 的 property
子组件不需要知道 inject 的 property 来自哪里
24. 动态组件
- 形如
<component :is="xxx"></component>
即可定义一个动态组件,动态组件的内容由xxx
决定,例如此时xxx
为vc1
组件,那么vc1
组件将被渲染在此处 - 动态组件在切换视图时,每一个被显示的组件都会被重新渲染,先前的状态不会被缓存,在某些场景下,这种特性不仅不符合需求,而且会加大性能负担
- 在组件外套一层
<keep-alive>...</keep-alive>
即可在该组件第一次被创建时将其缓存,后续该组件再被展示时将直接复用缓存中的数据 <keep-alive include="xx">
可以指定仅xx
组件被缓存,若指定多个写成数组即可- 被缓存的组件还有
activated
和deactivated
两个生命周期函数,分别在组件被激活和失活时调用
25. 异步组件
异步组件可以让组件在特定时候才被加载,适用于大型项目的功能拆分场景。
Vue2中异步组件写法:
app.component(
"asyncItem",
function (resolve, reject) {
setTimeout(() => {
resolve({
template: `<div>异步组件</div>`,
});
}, 4000);
});
})
);
Vue3异步组件需要使用defineAsyncComponent方法来创建:
app.component(
"asyncItem",
Vue.defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
template: `<div>异步组件</div>`,
});
}, 4000);
});
})
);
26. 过渡与动画
- Vue在插入、更新及移除节点时,可以触发相应的过渡或动画
- 使用
v-enter
定义进入的起点样式,使用v-enter-active
定义进入的过程样式,使用v-enter-to
定义进入的终点样式 - 使用
v-leave
定义退出的起点样式,使用v-leave-active
定义退出的过程样式,使用v-leave-to
定义退出的终点样式 - 使用
<transition name="xxx">...</transition>
包裹要过渡的单个元素,xxx
对应样式名的v
部分 - 若有多个元素需过渡,则需要使用
<transition-group>...</transition-group>
,并且每个元素都要指定key值 - v-enter和v-leave含义不明确,Vue3将过渡类名v-enter修改为v-enter-from,过渡类名v-leave修改为v-leave-from
27. Mixin
mixin
可以把多个组件共用的配置提取成一个混入对象- 定义一个js文件写入共用配置项,在组件内使用
mixins:['xxx']
混入 - 当普通配置项(例如data、methods)出现属性重叠时,以组件自定义的值为准
- 当生命周期函数出现重叠时,两者将共存,混入对象的生命周期函数会优先执行
- 组件内引入的mixin的配置项只能在该组件局部生效,子组件将无法使用,使用
Vue.mixin(xxx)
可以将配置项全局混入,全局混入时其它组件都可以接收到配置项,也无需使用mixins:['xxx']
声明 - 全局混入不便于维护,一般不推荐使用
请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项,就像上面示例一样。推荐将其作为插件发布,以避免重复应用混入。
- Vue2中当值为对象的属性发生重叠时,会进行深层次合并(即对象内部属性的也会合并),Vue3已变更为浅层次合并
- Mixin容易发生属性重叠,也不支持传递参数来控制逻辑,Vue3推荐使用Composition API来替代相关的用法
28. 自定义指令
- Vue允许用户自己定义类似
v-if
、v-model
的指令 Vue.directive("xxx", {...})
可以配置全局自定义指令,书写v-xxx
即可使用- 也可以配置局部自定义指令,在要使用的组件内配置
directives
选项即可 - 自定义指令体内可以定义生命周期函数,
bind
函数在指令第一次绑定到元素时调用,inseted
函数在元素被插入父节点时调用,update
函数在所在组件的虚拟节点更新后调用,componentUpdated
函数在所在组件及其子组件的虚拟节点全部更新后调用,unbind
是指令与元素解绑时调用 - 指令内定义的声明周期函数可以接收几个参数,
el
为所绑定的元素,binding
是包含指令参数值value
的一个对象 - 如果想让
bind
和update
触发相同的行为,可以直接用回调函数定义指令体,形如Vue.directive("xxx", (el, binding)=>{...})
- 指令使用方可以传参数值,还可以定义参数类型,类似
v-on:xxx="yyy"
,接收参数值用binding.value
,接收参数类型用binding.arg
- Vue2中指令生命周期的分类不够清晰,并且跟组件差距较大,加重了理解成本。在Vue3中,bind更名为beforeMount,inserted更名为mounted,并且将updated和componentUpdated合并为updated,增加了beforeUpdated和beforeMount,将unbind更名为unmounted,如此一来,指令的生命周期函数的分类基本跟组件类似了
29. Teleport(Vue3新)
- 如果想将组件内定义的一部分内容挂载到Vue接管的范围之外,可以使用Vue3新增的Teleport功能
- 将需要被传送的内容包裹在
<teleport to="xxx">...</teleport>
之中,xxx
为目的地,可以是标签名也可以id、类选择器 - Teleport并不会改变组件间原有的层级结构(从Vue的角度看就相当于发生了一场“海市蜃楼”)
30. render函数
- 在Vue编程中我们通常是使用
template
模板来定义视图的,当我们想要手动控制渲染过程时,或者我们使用了不包含模板解析器的Vue时,都需要使用render
函数来进行手动渲染 render
函数接收一个函数类型的参数,它用来创建一个用于描述虚拟节点(VNode)信息的元素,render
函数需要返回这个元素,完整的写法形如render(createElement){return createElement(...)}
,在脚手架中可能被简写为render: h=>h(...)
- 上文所指的
createElement
函数可以传入3个参数,从左至右,第一个参数可以传入HTML标签名(String)或者组件对象(Object),第二个参数可以传入一个属性对象,第三个参数可以传入一个子节点所构成的数组(也要由createElement
构建) - 我们常说的虚拟DOM就是指VNode所形成的树,它本质上就是由render函数创建的一个JS对象,该对象内包含各个节点的元素信息,并且反映了节点间的层级关系。我们的数据变动会实时映射到虚拟DOM上,Vue通过比对虚拟DOM和真实DOM的差异,在合适的时机采用代价最小的策略去一次性更新真实DOM
- 在Vue3中,render函数不再接收任何参数,createElement函数需要从vue对象上全局引入,并且统一用简写名h,导入方法import { h } from ‘vue’
31. 插件
- 插件可以将通用的功能封装起来在需要的时候调用,它可以在不破坏原先代码的情况下增强Vue的功能(像Vuex和Vue Router就是采用插件的形式作为Vue的扩展的)
- 书写包含
install
方法的一个对象即可定义一个插件,install的第一个参数是Vue构造函数,第二个以后的参数是插件使用者传递的数据 - 使用
Vue.use(xxx)
可以使用插件 - Vue3中install的第一个参数变为app实例
32. Composition API(Vue3新)
- Vue2中组件是由
data
、methods
、computed
、watch
、mixin
等配置项组织起来的,不同的配置项中有相关联的逻辑,当组件的代码量变得越来越庞大后,代码的阅读和维护会变得很困难
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
- Composition API是Vue3提出的新的编程方式,它摒弃了Vue2配置项列表的组织模式,主张将逻辑相关联的部分聚合在一块,并将原先配置项列表所实现的功能都抽离成函数,在需要的时候引入函数即可
33. setup函数(Vue3新)
setup
函数是一切Composition API的入口,Composition API需要在其上运作setup
函数在Vue生命周期的beforeCreate
阶段之前被执行,this指向undefined
(永远不要在setup
中考虑使用this)setup
接收两个参数,从左至右,第一个是props
对象,它包含组件外部传递过来且组件内部声明接收了的属性;第二个是context
对象,它包含了一些上下文信息,例如attrs
、slots
、emit
等setup
支持两种类型的返回值,第一种是返回普通对象,该普通对象包含需要向外暴露的各种属性,模板中可以直接引用这些属性;第二种是返回render
函数以便实现自定义渲染- 在传统的Vue2配置项内可以访问到
setup
暴露出去的内容,但在setup
内无法访问到传统配置项定义的内容,当setup
和传统配置项有内容重叠时,以setup
的内容为准(不推荐两种模式混用) - 默认情况下,
setup
不能声明成一个async函数(因为async函数的返回值是Promise对象,模板无法直接识别)
34. ref/reactive函数(Vue3新 + 重点)
ref
函数用于定义一个响应式的数据,它会返回RefImpl
对象,其中value
属性包含着目标数据- 在script中操作
RefImpl
对象需要使用xxx.value
,而在模板中则应直接写{{ xxx }}
,模板会自动识别 reactive
函数用于定义一个数组/对象类型的响应式数据,它会返回Proxy
对象(关于Proxy在上文第4点数据代理与劫持有提及)ref
函数在定义基本类型的数据时,沿用了Vue2的响应式策略——Object.defineProperty()
ref
函数在定义数组/对象类型的数据时,内部借助了reactive
函数(最终会返回一个包含Proxy
的RefImpl
对象)- 通常推荐用
ref
声明基本类型数据,用reactive
声明数组/对象类型数据
35. computed函数(Vue3新)
- Composition API中的
computed
函数与Vue2的computed
配置项的用法基本一致 computed
函数可以传入函数(计算属性的简写),也可以传入对象(计算属性的完整写法)
36. watch函数(Vue3新 + 重点)
watch
函数可以传入3个参数,从左至右,第一个参数可以是单个变量或多个变量组成的数组,第二个参数是回调函数(第一个形参是新值,第二个形参是旧值),第三个参数是一个配置对象(配置立即执行和深度监视)- 当
watch
函数监视一个或多个ref
定义的基本类型数据时,正常监听 - 当
watch
函数监视reactive
定义的数组/对象型数据时,无法正常获取旧值(新值和旧值相同),并且会强制开启深度监视(手动关闭无效) - 当
watch
函数监视reactive
定义的对象中的某个属性时,第一个参数需要改成返回该属性的函数,形如watch(()=>xx.yy, ()=>{}, {})
,监听多个时则放进数组中即可 - 当
watch
函数监视reactive
定义的对象中的某个对象属性时,需要手动开启深度监视(也就是说,仅当直接对reactive
定义的对象进行监视才会触发上述所说的强制深度监视) - 如果使用
ref
定义数组/对象类型的数据,在使用watch
监视时注意要监视ref对象的value
属性(该属性才是包含具体值的Proxy对象),或者手动开启深度监视
37. watchEffect函数(Vue3新)
watchEffect
函数相当于watch
的升级版,它不需要指定监听的对象,只要其回调函数中所依赖的属性发生变动,就会触发回调watchEffect
函数只用传入一个回调函数即可watchEffect
默认开启了立刻执行,且无法配置watchEffect
与conputed
的共同点是都是在其依赖的属性发生变化时做出回应,区别是computed
更注重多个属性所计算出来的结果,必须要返回一个值,而watchEffect
更注重去执行一个过程,不需要返回值
38. 生命周期函数(Vue3新)
beforeCreate
和created
没有对应的Composition API,这两个阶段整体性的被认为是setup
函数执行的时机
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
- 其余生命周期对应的Composition API就是在原先的名字前加上on,例如
mounted
对应的函数是onMounted
- 当同时存在配置项和Composition API时,两个函数都会执行,Composition API优先(不推荐两种模式混用)
- 除此之外,Vue3还新增了两个新的生命周期API:
onRenderTracked
能够跟踪所有状态,当页面发生更新时会产生事件;onRenderTriggered
仅会跟踪发生变化的状态
39. toRef/toRefs函数(Vue3新)
toRef
函数用于定义一个响应式的数据,它与ref
不同在于,ref
操作的是基本数据/数组/对象,而toRef
操作的是对象身上的属性- 语法形如
toRef(xxx, "yyy")
,xxx
为目标对象,yyy
为该对象的某个属性 - 直接使用
ref
去定义对象身上的属性,所创建的是一个全新的RefImpl,它的值与源对象没有关联,而使用toRef
去定义时,会创建ObjectRefImpl,它的值会引用源对象的属性 - 当需要将一个对象的所有属性一次性定义时,可以使用
toRefs
,只需要传入该对象即可,它会返回一个名为属性名、值为ObjectRefImpl的键值对象
40. provide/inject函数(Vue3新)
provide
函数需要传入2个参数,从左至右,第一个参数为要递交的数据名,第二个参数为要递交的数据,形如provide("xxx", yyy)
inject
函数有1个必传参数和1个可选参数,从左至右,第一个必传参数为要接收的数据名,第二个可选参数为默认值(在接收不到数据的时候启用)- provide提供的如果是ref/reactive生成的响应式数据,那么子组件在接收到该数据后,可以修改数据(不推荐这么做,尽可能还是通过调用父组件的事件去间接修改数据)
- 如果想让provide提供的ref/reactive数据不被修改,可以使用
readonly
函数再进行一层封装(返回引用源数据的Proxy对象)
41. Vuex
- Vuex是一个状态管理插件,它能够统一管理组件所共用的状态
- 所谓的状态(state)即应用所依赖的数据,用户在视图(view)上的动作(action)会导致状态的变化(mutation),状态变化后Vue又会重新渲染视图
- Vuex适用场景:当多个组件依赖同一状态,或者不同组件的行为需要变更同一状态时
- 执行流程:
① 通过new Vuex.Store({...})
函数创建状态仓库,需要定义三个属性,第一个属性actions
内定义用于响应用户动作的回调函数,第二个属性mutations
内定义改变状态的方法,第三个属性state
是状态对象
② 通过this.$store.dispatch("xxx", yyy)
来触发action,xxx
为action的回调名,yyy
为参数值
③ action对应的回调中接收两个参数,第一个参数是上下文对象(此处为content
),第二个参数是参数值,在处理完逻辑后需要通过content.commit("zzz", yyy)
来触发mutation的方法,zzz
为mutation的回调名
④ mutation对应的方法接收两个参数,第一个参数是状态对象state
,第二参数也是yyy
,最终的状态修改就是发生在此处 - 按照规范,mutation中不能存在异步操作,只能单纯操作状态,这样有利于调试工具进行快照,异步操作应统一放在action中完成(.dispatch => action => .commit=> mutation => state)
- 如果不需要经过具体的业务,只想单纯修改状态的话,那么可以省略dispatch环节直接进行commit
- 当state中的数据需要经过处理后使用时,可以增加一个
getters
配置项,里面用类似计算属性的形式去定义函数,函数的参数为状态对象state mapState
函数可以映射状态对象state中的数据为计算属性,mapGetters
函数可以映射getters对象中的数据为计算属性,mapActions
函数用于生成调用dispatch的方法体,mapMutations
函数用于生成调用commit的方法体,它们都需要通过import {...} from 'vuex'
引入,可以传入两种参数,传入对象时需分别指定方法名和对应store中的名字,传入数组时视为两者同名(注:使用mapActions
和mapMutations
生成的方法会从模板接收参数)- 当应用的组件变多后,将所有组件的配置项都放在一起不便于维护,可以将应用按状态划分为一个个模块,每一个模块定义单独的actions、mutations及state,最后用
new Vuex.store({module:{...}})
来创建store即可
42. Vue-Router
- Vue-Router是用于实现单页应用的路由管理插件
- 所谓的单页应用(SPA),指整个应用只有一个完整的页面,用户点击页面中的导航并不会做跳转,而是利用AJAX技术做局部重载
- 所谓的路由(Router)就是一组映射关系,键为唯一路径(例如URL),值为某个函数/方法或者组件(例如JavaEE中的Servlet,SpringMVC中的Controller,Vue.js中的Component),通过路由就可以寻找到特定服务的入口
- 使用方法:
① 定义路由组件:包含多个配置对象的数组,path
规定访问路径(必须),component
规定对应的组件(必须),name
规定路由的名称,children
规定子路由
② 注册路由组件:使用createRouter
函数创建路由,传入一个配置对象,history
规定路由模式,routes
绑定对应的路由组件
③ 使用路由:<router-link to="xxx">...</router-link>
- 路由可以通过
query
传参或者params
传参,当使用:to
的对象式进行params
传参时需要使用命名路由 - 当路由发生切换时,被隐藏的路由组件在默认情况下会被销毁,下次重新显示时会再次创建
- 每一个路由组件都有着
$route
和$router
两个属性,$route
是该路由组件自身的信息对象,而$router
是VueRouter的全局对象 <router-link>
最终会转换成<a>
标签- 编程式路由导航可以借助
$route.push
、$route.replace
、$route.forward
、$route.back
及$route.go
- 路由有Hash模式和History模式。Hash模式会在URL中添加
/#/哈希值
,该部分内容不会随HTTP请求发送给服务器,兼容性好,不需要依赖服务端,但是有URL不合法的风险;History模式下URL不会出现/#/
,但是会随HTTP请求发送给服务器,需要服务端做出支持
43. 路由守卫
- 路由守卫可以对路由进行权限控制(尤其在网站登录校验上),路由守卫有全局守卫、独享守卫和组件内守卫
- 使用
router.beforeEach((to,from,next)=>{...}
可以定义一个全局前置守卫,它会在初始化和每次路由切换前执行,to
包含了目标路由的信息,from
包含了源路由的信息,next
是一个触发向下执行的函数 - 使用
router.afterEach((to,from)=>{...}
可以定义一个全局后置守卫,它会在初始化和每次每次路由切换后执行 - 在定义路由组件的时候可以通过
beforeEnter(to,from,next){...}
来定义一个独享路由,其只会在所在路由切换前执行 - 在组件内通过
beforeRouteEnter(to,from,next){...}
或beforeRouteLeave(to,from,next){...}
来定义一个组件内路由,进入/离开该组件时被执行
44. script setup语法糖(Vue3.2新)
- 在Vue3的
setup()
书写方式虽然解决了Vue2逻辑分离的问题,但由于每次都需要手动将模板使用到的数据逐一暴露出去,依旧会造成代码的繁琐 <script setup>
也是Vue3提出的一种新标准,它被正式启用是在Vue3.2版本,<script setup>
直接取代整个<script>
标签,顶层作用域下的变量会被自动暴露给模板,因此无需再自己维护冗长的return {...}
语句<script setup>
本质上是setup()
函数的语法糖,内部的代码会被编译成setup()
函数的内容,因此,该标签会在每次组件实例被创建的时候执行<script setup>
下引入的组件可以直接在模板中使用<script setup>
必须使用defineProps
来声明props,使用defineEmits
来声明emits(它俩不是函数,只是编译器宏,无需引入即可使用)- 使用
<script setup>
声明的组件不会向父组件暴露(即父组件无法在模板中通过ref
引用它),需要使用编译器宏defineExpose
声明需要暴露的属性