Vue.js知识点大复盘(Vue2对照Vue3新特性)

这篇文章是个人学习时记录的要点汇总,因为省略了代码示例,更没有覆盖完整知识面,所以仅用作学习过后的复习材料,系统学习应参考Vue.js官方文档(本文的几个引用段落均摘自该文档)
本文知识点以Vue2为基准,并补充了Vue3的内容作为对照,现有特性的变动会在正文中用荧光笔标注,新加特性会在标题上注明Vue3新,一些整体性变动已在开篇的前置说明中提及,另外,个人认为的易错点及需要特别注意的地方会在标题上注明重点

前置说明

在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.componentapp.configapp.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中定义的方法不同,计算属性的方法只会在依赖的数据发生变化时重新执行,而普通方法会在页面重新渲染时就会执行
  • 因为计算属性有缓存机制,所以当methodscomputed都能实现同一个功能时,推荐使用computed以减少不必要的运算

7. 监听属性

  • watch属性对象中定义监听属性的方法,属性名应与被监听的属性一致
  • 使用handler(newValue,oldValue)方法操作逻辑
  • 当被监视的属性变化时, 回调函数自动调用
  • 可配置immediate:true使得页面渲染时即执行一次逻辑
  • 可配置deep:true开启深度监视(能够监听到对象内部值的变化)
  • 当不需要配置多余参数时,可以将监听对象简写成单个函数(形式参照handler方法)
  • computed能够实现的功能watch都能够实现,除此之外watch能够实现异步操作

8. this指向问题(重点)

  • 所有被Vue直接管理的函数(例如methodscomputedwatch中定义的函数),尽量使用普通函数定义,因为普通函数的this会被自动指向vm实例或组件实例,而箭头函数的this会指向Window
  • 所有不被Vue直接管理的函数(例如定时器的回调函数、Ajax的回调函数),尽量使用箭头函数定义,这样this会自动向上匹配到vm实例或组件实例

9. 绑定样式

  • :class可绑定class样式,可书写字符串、数组、对象
  • :style可绑定style样式,可书写数组或对象

10. 条件渲染

  • v-if控制DOM元素的加载与卸载,当其值为假时将该元素从DOM结构中移除
  • v-if可与v-else-ifv-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实例
  • 组件中datamethods等函数的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决定,例如此时xxxvc1组件,那么vc1组件将被渲染在此处
  • 动态组件在切换视图时,每一个被显示的组件都会被重新渲染,先前的状态不会被缓存,在某些场景下,这种特性不仅不符合需求,而且会加大性能负担
  • 在组件外套一层<keep-alive>...</keep-alive>即可在该组件第一次被创建时将其缓存,后续该组件再被展示时将直接复用缓存中的数据
  • <keep-alive include="xx">可以指定仅xx组件被缓存,若指定多个写成数组即可
  • 被缓存的组件还有activateddeactivated两个生命周期函数,分别在组件被激活和失活时调用

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-ifv-model的指令
  • Vue.directive("xxx", {...})可以配置全局自定义指令,书写v-xxx即可使用
  • 也可以配置局部自定义指令,在要使用的组件内配置directives选项即可
  • 自定义指令体内可以定义生命周期函数,bind函数在指令第一次绑定到元素时调用,inseted函数在元素被插入父节点时调用,update函数在所在组件的虚拟节点更新后调用,componentUpdated函数在所在组件及其子组件的虚拟节点全部更新后调用,unbind是指令与元素解绑时调用
  • 指令内定义的声明周期函数可以接收几个参数,el为所绑定的元素,binding是包含指令参数值value的一个对象
  • 如果想让bindupdate触发相同的行为,可以直接用回调函数定义指令体,形如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中组件是由datamethodscomputedwatchmixin等配置项组织起来的,不同的配置项中有相关联的逻辑,当组件的代码量变得越来越庞大后,代码的阅读和维护会变得很困难

这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。

  • Composition API是Vue3提出的新的编程方式,它摒弃了Vue2配置项列表的组织模式,主张将逻辑相关联的部分聚合在一块,并将原先配置项列表所实现的功能都抽离成函数,在需要的时候引入函数即可

33. setup函数(Vue3新)

  • setup函数是一切Composition API的入口,Composition API需要在其上运作
  • setup函数在Vue生命周期的beforeCreate阶段之前被执行,this指向undefined(永远不要在setup中考虑使用this)
  • setup接收两个参数,从左至右,第一个是props对象,它包含组件外部传递过来且组件内部声明接收了的属性;第二个是context对象,它包含了一些上下文信息,例如attrsslotsemit
  • 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函数(最终会返回一个包含ProxyRefImpl对象)
  • 通常推荐用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默认开启了立刻执行,且无法配置
  • watchEffectconputed的共同点是都是在其依赖的属性发生变化时做出回应,区别是computed更注重多个属性所计算出来的结果,必须要返回一个值,而watchEffect更注重去执行一个过程,不需要返回值

38. 生命周期函数(Vue3新)

  • beforeCreatecreated没有对应的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中的名字,传入数组时视为两者同名(注:使用mapActionsmapMutations生成的方法会从模板接收参数)
  • 当应用的组件变多后,将所有组件的配置项都放在一起不便于维护,可以将应用按状态划分为一个个模块,每一个模块定义单独的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声明需要暴露的属性
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值