Vue.js 3应用开发与核心源码解析

元数据

Vue.js 3应用开发与核心源码解析

  •  Vue.js 3应用开发与核心源码解析|200
  • 书名: Vue.js 3应用开发与核心源码解析
  • 作者: 吕鸣
  • 简介: 本书以前端工程化和企业级应用开发为目标,围绕Vue 3及相关生态技术与核心源码进行详细剖析。内容包括:Vue.js核心基础;相关生态,包括状态管理框架Vuex、路由管理框架Vue Router、Vue动画技术、Vue网络与数据存储技术,前端构建工具Vite与Vue Cli;进阶的Vue服务端渲染,包括Node.js、Express和Nuxt.js;核心源码剖析,包括响应式原理、双向绑定实现、虚拟DOM、keep-alive原理和实现,旨在使读者掌握Vue的设计思想,提升开发项目和应对面试的能力;每章提供一个小项目,最后还提供了一个以工程化思想开发的实战项目,使读者能够真正掌握从0到1开发一个企业级应用的全过程。
  • 出版时间 2022-08-01 00:00:00
  • ISBN: 9787302612629
  • 分类: 计算机-编程设计
  • 出版社: 清华大学出版社
  • PC地址:https://weread.qq.com/web/reader/24132000813ab742bg012b46

高亮划线

1.1 认识MVC和MVVM模式

📌 传统的MVC模式包括以下三部分:· 视图(View):用户界面。· 控制器(Controller):业务逻辑。· 模型(Model):数据存储。Model代表数据存储,主要用于实现数据的持久化;View代表用户界面(UI),主要用于实现页面的显示;Controller代表业务逻辑,串联起View和Model,主要用来实现业务的逻辑代码。 ^3300034992-7-810-1095

  • 💭 在MVC模式中,用户的交互行为在View中触发,由View通知Controller去进行对应的逻辑处理,处理完成之后通知Model改变状态,Model完成状态改变后,找到对应的View去更新用户界面的显示内容,至此完成对用户交互行为的反馈 - ⏱ 2024-10-24 18:14:43

📌 由此可见,整个流程由View发起,最终在View中做出改变,这是一个单向的过程。当年流行的backbone.js就是MVC的典型代表。
⏱ 2024-10-24 18:13:37 ^3300034992-7-1215-1282

📌 MVVM是把MVC中的Controller去除了,相当于变薄了,取而代之的是ViewModel。所谓ViewModel,是一个同步的View和Model的对象,在前端MVVM中,ViewModel最典型的作用是操作DOM,特点是双向数据绑定(Data-Binding)。 ^3300034992-7-1391-1526

  • 💭 在双向数据绑定中,开发者无须关注如何找到DOM节点和如何修改DOM节点,因为每一个在View中需要操作的DOM都会有一个在Model中对应的对象,通过改变这个对象,DOM就会自动改变;反之,当DOM改变时,对应的Model中的对象也会改变。ViewModel将View和Model关联起来,因此开发者只需关注业务逻辑,不需要手动操作DOM,这就是ViewModel带来的优势 - ⏱ 2024-10-24 18:16:37

1.4 Vue 3新特性概览

📌 ,相较于Vue 2.x版本,Vue 3主要有以下几个方面大的改动以及提升:· 更快。· 更小。· 更易于维护。
⏱ 2024-10-24 18:20:42 ^3300034992-10-521-663

📌 更快主要体现在Vue 3在性能方面的提升,以及在源码层面的改动,主要包括以下方面:· 重构虚拟DOM。· 事件缓存。· 基于Proxy的响应式对象。
⏱ 2024-10-24 18:20:52 ^3300034992-10-936-1097

📌 标记静态内容,并区分动态内容(静态提升)。· 更新时只diff动态的部分。
⏱ 2024-10-24 18:21:57 ^3300034992-10-1852-1918

📌 在Vue 3中,提供了事件缓存对象cacheHandlers,当cacheHandlers开启的时候,@click绑定的事件会被标记成静态节点,被放入cacheHandlers中,这样在视图更新时也不会追踪,当事件再次触发时,就无须重新生成函数,直接调用缓存的事件回调方法即可,在事件处理方面提升了Vue的性能。
⏱ 2024-10-24 18:23:33 ^3300034992-10-2167-2323

📌 在Vue 2.x中,使用Object.defineProperty()来实现响应式对象,对于一些复杂的对象,需要循环递归地给每个属性增加getter/setter监听器,这使得组件的初始化非常耗时,而Vue 3中,引入了一种新的创建响应式对象的方法reactive,其内部就是利用ES 6的Proxy API来实现的,这样就可以不用针对每个属性来一一进行添加,以减少开销,提升性能。我们会在后续章节具体讲解Vue 3的响应式和Proxy API。
⏱ 2024-10-24 18:23:49 ^3300034992-10-3148-3371

📌 Monorepo是一种管理代码的方式,它的核心观点是所有的项目在一个代码仓库中,但是代码分割到一个个小的模块中,而不是都放在src这个目录下
⏱ 2024-10-24 18:25:39 ^3300034992-10-5831-5901

📌 在服务端渲染方面,Vue 3优化了返回HTML字符串的逻辑。在Vue 2.x中,所有的节点(包括一些静态节点)在服务端返回时都会转换为虚拟DOM,再转换成HTML字符串返回给浏览器;Vue 3则将静态节点剥离成字符串,这部分内容不会转换成虚拟DOM,而是直接拼接返回,在效率上进行了提升。
⏱ 2024-10-24 18:27:39 ^3300034992-10-8283-8427

1.5 ES 6语言基础

📌 箭头函数会默认绑定外层的上下文对象this的值,因此在箭头函数中,this的值和外层的this是一样的,不需要使用bind或者call的方法来改变函数中的上下文对象
⏱ 2024-10-24 18:30:06 ^3300034992-11-2871-2953

2.1 Vue.js实例和组件

📌 Vue.js的组件化是指每个组件控制一块用户界面的显示和用户的交互操作,每个组件都有自己的职能,代码在自己的模块内互相不影响,这是使用Vue.js的一大优势。
⏱ 2024-10-24 18:37:24 ^3300034992-15-8161-8240

2.2 Vue.js模板语法

📌 关于虚拟DOM的概念,可以简单理解成Vue在每次把数据更新到用户界面时,都会在内部事先定义好前后两个虚拟的DOM,一般是对象的形式。通过对比前后两个虚拟DOM的异同来针对性地更新部分用户界面,而不是整体更新(没有改变的用户界面部分不去修改,这样可以减少DOM操作,提升性能)。
⏱ 2024-10-24 18:52:18 ^3300034992-16-11385-11523

📌 自定义事件一般用在组件通信中,我们会在后面的章节讲解,在使用v-on监听原生DOM事件时,可以添加一些修饰符并有选择性地执行一些方法或者程序逻辑:· .stop:阻止事件继续传播,相当于调用event.stopPropagation()。· .prevent:告诉浏览器不要执行与事件关联的默认行为,相当于调用event.preventDefault()。· .capture:使用事件捕获模式,即元素自身触发的事件先在这里处理,然后才交由内部元素进行处理。· .self:只有当event.target是当前元素自身才触发处理函数。· .once:事件只会触发一次。· .passive:告诉浏览器不阻止与事件关联的默认行为,相当于不调用event.preventDefault()。与prevent相反。· .left、.middle、.right:分别对应鼠标左键、中键、右键的单击触发。· .{keyAlias}:只有当事件是由特定按键触发时才触发回调函数。
⏱ 2024-10-24 18:54:16 ^3300034992-16-13931-14596

📌 · .lazy:在默认情况下,v-model会同步输入框中的值和数据。可以通过这个修饰符,转变为在输入框的change事件中再进行值和数据同步。· .number:自动将用户的输入值转化为number类型。· .trim:自动过滤用户输入的首尾空格。
⏱ 2024-10-24 18:59:10 ^3300034992-16-17602-17785

📌 在之前讲解v-for指令时,我们知道key属性给每个列表元素分配了唯一的键值,这样使得Vue在做前后的虚拟DOM改变对比时更加高效,但前提是需要创建新的虚拟DOM,当我们使用了v-memo时,如果当前的列表元素所对应的v-memo没有改变,那么这部分虚拟DOM也不会重新创建,减少了过多的虚拟DOM创建,也能在一定程度上提升性能。当然,这在列表条数很少时体现得并不明显,但是当列表很长时,也就能体现出性能差异了。
⏱ 2024-10-25 12:22:16 ^3300034992-16-18887-19093

📌 把用方括号括起来的JavaScript表达式作为一个v-bind或v-on指令的参数,这种参数被称为动态参数。
⏱ 2024-10-25 12:26:03 ^3300034992-16-19597-19652

📌 动态参数表达式有一些语法约束,因为某些字符(例如空格和引号)放在HTML属性名里是无效的,所以要尽量避免使用这些字符
⏱ 2024-10-25 12:26:19 ^3300034992-16-20203-20261

📌 变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂的表达式。另外,如果在DOM中使用模板(直接在一个HTML文件中编写模板需要回避大写键名),需要注意浏览器会把属性名全部强制转为小写
⏱ 2024-10-25 12:26:37 ^3300034992-16-20415-20513

2.3 Vue.js的data属性、方法、计算属性和监听器

📌 · 计算属性:只要依赖的数据没发生改变,就可以直接返回缓存中的数据,而不需要每次都重复执行数据操作。· 方法:只要页面更新用户界面,就会发生重新渲染,methods调用对应的方法,执行该函数,无论是不是它所依赖的。
⏱ 2024-10-30 07:53:47 ^3300034992-17-6942-7078

📌 如果需要监听一个属性的改变,并且在改变的回调方法中有一些异步的操作或者数据量比较大的操作,这时应当使用监听属性watch。而对于简单的同步操作,使用计算属性computed更加合适。
⏱ 2024-10-30 08:23:25 ^3300034992-17-14597-14688

3.1 组件生命周期

📌 1.beforeCreate方法这个阶段在实例初始化之后,数据观测(Data Observer)和event/watcher事件配置之前被调用。需要注意的是,这个阶段无法获取到Vue组件data中定义的数据,官方也不推荐在这里操作data,如果确实需要获取data,可以从this.$options.data()获取。
⏱ 2024-10-30 08:24:50 ^3300034992-21-2245-2434

📌 2.created方法在beforeCreate执行完成之后,Vue会执行一些数据观测和event/watcher事件的初始化工作,将数据和data属性进行绑定以及对props、methods、watch等进行初始化,另外还要初始化一些inject和provide。
⏱ 2024-10-30 08:24:57 ^3300034992-21-2501-2664

📌 1.beforeMount方法前文提到了el和template属性以及render函数,render函数用于给当前Vue实例挂载DOM(Vue组件渲染HTML内容),这里的beforeMount就是渲染前要执行的程序逻辑。
⏱ 2024-10-30 08:25:48 ^3300034992-21-3615-3756

📌 2.mounted方法这个阶段开始真正地执行render方法进行渲染,之前设置的el会被render函数执行的结果所替换,也就是说将结果真正渲染到当前Vue实例的el节点上,这时就会调用mounted方法。
⏱ 2024-10-30 08:25:55 ^3300034992-21-3823-3956

📌 但是需要注意的是,mounted不会保证所有的子组件也都一起被挂载。如果读者希望等到整个视图都渲染完毕,可以在mounted内部使用this.$nextTick ^3300034992-21-4480-4560

  • 💭 nextTick 是等到视图中的所有组件都渲染完 - ⏱ 2024-10-30 08:26:25

📌 1.beforeUpdate方法当Vue实例data中的数据发生了改变,就会触发对应组件的重新渲染,这是双向绑定的特性之一,所以数据改变就会触发beforeUpdate方法。
⏱ 2024-10-30 08:27:23 ^3300034992-21-4955-5072

📌 2.updated方法当执行完beforeUpdate方法后,就会触发当前组件挂载DOM内容的修改,当前DOM修改完成后,便会触发updated方法,在updated方法中可以获取更新之后的DOM。
⏱ 2024-10-30 08:27:28 ^3300034992-21-5139-5268

📌 1.beforeUnmount方法beforeUnmount方法在组件卸载之前调用。在这一步,实例仍然完全可用。 ^3300034992-21-8410-8496

  • 💭 vue2中的 beforeDestroy 和 destroyed 这两个生命周期钩子在 vue3 中分别被重命名为 beforeUnmount 和 unmounted - ⏱ 2024-10-30 08:28:19

📌 2.unmounted方法unmounted方法在Vue组件卸载后调用。调用后,Vue组件关联的所有事件监听器都会被移除,所有的当前组件的子组件也会被销毁。
⏱ 2024-10-30 08:28:22 ^3300034992-21-8563-8671

📌 errorCaptured该方法表示当捕获一个来自当前子孙组件的错误时被触发,注意当前组件报错不会触发。这里的报错一般只会限制在当前Vue根实例下代码所抛出的DOMException或者异常Error对象(new Error())等错误,如果是Vue之外的代码,是不会触发的。该方法会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。在某个子孙组件的errorCaptured返回false时,可以阻止该错误继续向上传播。另外,也可以在全局配置errorCaptured,这样就可以监听到所有属于该根实例的报错信息,配置如示例代码3-1-4所示。
⏱ 2024-10-30 08:32:30 ^3300034992-21-10390-10735

📌 activated表示当vue-router的页面被打开时,会触发这个钩子函数。deactivated表示当vue-router的页面被关闭时,会触发这个钩子函数。
⏱ 2024-10-30 08:33:03 ^3300034992-21-11739-11821

📌 renderTracked和renderTriggered ^3300034992-21-11891-11920

  • 💭 renderTracked 和 renderTriggered 是vue3中两个新增的生命周期钩子,主要用于调试和性能优化。

    1. onRenderTracked:这个钩子函数会在组件的渲染跟踪列表中触发。它用于跟踪页面上所有的方法和变量,也就是我们 return 返回的属性和方法,它都会进行跟踪。当页面有 update 时,会生成一个 event 对象,通过这个 event 对象可以查看程序的问题所在。这个钩子可以帮助开发者了解哪些响应式状态被追踪了,从而优化渲染性能。

    2. onRenderTriggered:这个钩子函数会在组件的渲染触发器列表中触发。与 onRenderTracked 不同,它不会跟踪每一个值,而是跟踪发生变化值得信息,并且新值和旧值都会展示出来。这个钩子函数可以用于执行副作用操作,比如在状态变化时执行特定的逻辑 - ⏱ 2024-10-30 08:34:34

3.2 组件通信

📌 如果props传递的是一个动态值,每次父组件的info发生更新时,子组件中接收的props都将会刷新为最新的值。这意味着,我们不应该在一个子组件内部改变props
⏱ 2024-10-30 08:37:42 ^3300034992-22-4668-4749

📌 Vue中父传子的方式形成了一个单向下行绑定,叫作单向数据流。父级props的更新会向下流动到接收这个props的子组件中,触发对应的更新,但是反过来则不行。这样可以防止有多个子组件的父组件内的值被修改时,无法查找到哪个子组件修改的场景,从而导致应用中的数据流向无法清晰地追溯。
⏱ 2024-10-30 08:38:06 ^3300034992-22-5176-5314

📌 在父组件的data中定义了info属性,并且通过v-model的方式传给了子组件,代码如下:这里使用了$emit方法,在子组件中,给按钮button绑定了一个单击事件,在事件回调函数中,采用如下代码:this.$emit(‘update:info’,’Tom’)
⏱ 2024-10-30 08:45:17 ^3300034992-22-10011-10248

📌 调用$emit方法实际上就是触发一个父组件的方法,这里的update是固定写法,代表更新,而:info表示更新info这个prop,第二个参数Tom表示更新的值
⏱ 2024-10-30 08:45:24 ^3300034992-22-10299-10379

📌 D组件中通过$emit调用父组件的方法,同时在父组件中修改data中的infoFromD,同时也影响到了作为props传递给C组件的infoFromD,这就实现了兄弟组件的通信。
⏱ 2024-10-30 08:46:15 ^3300034992-22-11526-11615

📌 事件总线EventBus和mitt在Vue 2中,可以采用EventBus这种方法,实际上就是将沟通的桥梁换成自己,同样需要有桥梁作为通信中继。就像是所有组件共用相同的事件中心,可以向该中心发送事件或接收事件,所有组件都可以上下平行地通知其他组件。
⏱ 2024-10-30 08:46:39 ^3300034992-22-11759-11913

📌 在Vue 3中,由于取消了Vue中全局变量Vue.prototype.$EventBus这种写法,所以采用EventBus这种事件总线来进行通信已经无法使用,取而代之,可以采用第三方事件总线库mitt。
⏱ 2024-10-30 12:36:50 ^3300034992-22-12742-12843

📌 通常,当需要从父组件向子组件传递数据时,我们使用props。想象一下这样的结构:有一些深度嵌套的组件,深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将props沿着组件链逐级传递下去,可能会很麻烦。对于这种情况,我们可以使用一对provide(提供)和inject(注入)。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。
⏱ 2024-10-30 12:38:02 ^3300034992-22-13770-13975

📌 provide/inject绑定并不是响应式的。我们可以通过传递一个ref属性或reactive对象给provide来改变这种行为
⏱ 2024-10-30 12:40:11 ^3300034992-22-15827-15892

3.3 组件插槽

📌 在中使用v-bind来绑定person这个值,我们称这个值为插槽的props,就是标识出这个插槽想要将person带给外部父组件使用。同时,在父组件上,可以给