当前位置: 首页 > Web前端 > vue.js

越来越流行的Vue你想学吗?90后小姐姐今天教你

时间:2023-03-31 15:59:45 vue.js

摘要:Vue的相关技术原理已经成为前端求职面试的必修知识点。掌握Vue对于前端工程师来说更是重要。这就像一门“必修课”。本文原作者殷婷,擅长前端组件库开发和微信机器人。我们发现Vue越来越受欢迎。无论是BAT大厂还是初创公司,Vue的应用都非常广泛。与Angular和React相比,这三者都是优秀的前端框架,但从GitHub来看,Vue已经达到了170万的Star。Vue的相关技术原理也成为了前端求职面试的必考知识点。掌握Vue更像是前端工程师的“必修课”。为此,华为云社区邀请了90后前端开发工程师殷婷来分享《Vue3.0新特性介绍以及搭建一个vue组件库》。了解Vue3.0,先从六大特性说起。Vue.js是一个JavaScriptMVVM库和一个用于构建用户界面的渐进式框架。2019年10月5日凌晨,Vue3的源码alpha。目前正式版已经发布。据作者介绍,Vue3.0有六大特点:TreeShaking;作品;分段;传送;悬念;渲染性能。RenderingPerformance主要是框架内部的性能优化,比较底层。本文将主要介绍前四个特征的解读。TreeShaking大多数编译器会为我们的代码执行死代码删除。首先我们要明白,什么是死代码?具有以下特点的代码,我们称之为死代码:代码不会被执行,无法到达;代码执行的结果不会被使用;该代码只会影响死变量(只写但不读)。比如我们给一个变量赋值,但是不使用这个变量,那么这就是一个死变量。这是我们将删除它的定义阶段的一部分,例如汇总以消除死代码的工作。如上例所示,左边是开发源码提供的两个函数,但最终只用到了baz函数。在最后的打包中,会去掉foo函数,只将baz函数打包到浏览器中运行。TreeShaking是一种淘汰死代码的方法,更侧重于淘汰无用模块,淘汰那些被引用但未被使用的模块。左边的代码中,export有两个功能,一个是post,一个是get,但是我们生产中真正用到的只有post。然后rollup打包之后,直接去掉get函数,然后只有post函数打包到我们的生产中。除了rollup支持这个特性,webpack也支持。接下来我们看看VUE3.0为支持TreeShaking做了哪些工作?首先对比下nextTick与VUE2和VUE3的使用:VUE2在VUE实例上挂载nextTick到全局API;VUE3先把nextTick模块去掉,等要用到的时候再引入这个模块。通过这个对比可以看出,在使用VUE2时,即使没有nextTick等方法,由于是GLOBAAPI,也必须挂载在实例上。当最终打包生产代码时,这个函数会被打包进去,这段代码也会影响文件大小。如果在VUE3.0中不需要这个模块,那么最终打包的文件中不会有这一段代码。这样,最终生产代码的大小就减少了。当然不仅仅是nextTick,VUE3.0内部还有很多其他的tree-shaking。例如:如果你不使用keep-alive组件或v-show命令,它会引入很多与keep-alive或v-show无关的包。上图是Vue2.0的代码。左边是介绍utils函数,然后把这个函数称为mixins。这段代码是Vue2中最常用的,但是这段代码是有问题的。如果你对这个项目不熟悉,当你第一次看到这段代码的时候,因为你不知道这个utils里面有哪些属性和方法,也就是说这个mixins对于开发者来说是一个黑盒子。很容易遇到一个场景:在组件开发的早期,应用了一种mixins的方法,但是现在已经没有必要使用这种方法了。删除过程中,发现不知道其他地方是否引用了mixins的其他属性和方法。如果Composition使用Vue3.0Composition,如何避免这个问题?如上图,假设是一个组件实例,我们使用useMouse函数,返回两个变量X和Y。从左边的代码可以看出useMouse函数是root。监听鼠标移动事件后,返回鼠标的XY坐标。通过这样组织代码,可以清楚的知道这个函数返回的变量和变化的值。接下来我们看一个Composition的例子:左边是Vue2中最常用的一段代码。首先在数据中声明名字和姓氏,然后在回复的时候请求接口,获取接口返回值。在computed然后得到他的全名。那么,这段代码有什么问题呢?在这里计算,因为我们不知道返回的全名的逻辑是什么。拿到数据后,希望通过数据的返回值得到它的名字和姓氏,进而得到它的全名。但是这段代码的逻辑在获取到接口之后就已经断掉了。这是Vue2.0的不合理设计,导致我们的逻辑被分成了两种配置。那么,如果使用Composition,如何实现呢?请求接口后,直接获取其返回数据,然后将返回数据的值赋值给计算函数,这里可以获取全名。从这段代码可以看出,逻辑更加聚合。useMouse函数怎么用,里面的变量也是响应式的。Vue3.0中提供了两个函数:reactive和ref。Reactive可以传入一个对象,然后这个函数返回后的状态就是responsive;ref直接传入一个值,然后返回给view对象,也是响应式的。如果我们在setup函数中返回一个可以响应值的对象,就可以在渲染字符串模板的时候使用。比如有时候我们直接修改数据,视图也会随之改变。在Vue2中,一般使用mixin来复用逻辑代码,但是存在一些问题:比如代码来源不明,方法属性冲突等。基于此,vue3中引入了CompositionAPI(组合API),使用纯函数来分离重用代码,这与React中的钩子概念非常相似。Composition的好处是暴露给模板的属性来源明确,是函数返回的;第二,可以重用逻辑;第三,返回值可以任意命名,不存在秘密空间冲突;第四,不会因为额外的组件强度而造成性能损失。以前我们想要获取一个响应式的数据,就得把数据放在组件中,然后在数据中声明,这样对象才能响应式。现在我们可以直接使用reactive和ref函数保证可以做出响应式。Fragment在写vue2的时候,因为组件必须只有一个根节点,所以经常会添加一些无意义的节点进行包裹。Fragment组件就是用来解决这个问题的(这点和React中的Fragment组件是一样的)。Fragment其实在Vue2的一个群房里。它的模板必须有一个根DIV来包裹它,然后把你写在里面。在Vue3中,我们不需要这个根DIV来包装这个组件。上图是2和3的对比,TeleportTeleport其实就是React中的Portal。Portal提供了一个极好的解决方案,用于将子节点渲染到存在于父组件外部的DOM节点。Teleport提供了一个Teleport组件,它指定了一个目标元素,比如这里指定了body,那么Teleport的任何内容都会被渲染到这个目标元素中,也就是说下面的部分Teleport代码会直接渲染到身体。那么关于Teleport应用程序的位置,我们可以举个例子来说明。比如我们在做组件的时候,经常会实现一个对话框。对话框的背景是黑色的全屏DIV,我们的布局是position:absolute。如果父元素是相对布局,我们的背景层会受到其父元素的影响。所以这时候如果使用Teleport直接将父组件设置为body,这样就不会再受子组件元素样式的影响,就可以确定一个我们想要的黑色背景画了。接下来写一篇react和vue的diff算法的对比。我一边写代码,一边写文章,整理思路。注意:这里只讨论标签属性相同且有多个children的情况。如果直接替换或删除不同的标签,就没什么好写的了。用这个例子来说明:simplediff,删除原来的,插入更新的。改变前后的标签都是li,所以只需要比较vnodeData和children就可以复用原来的DOM。从这个例子开始,我只需要遍历旧的vnode,然后patch旧的vnode和新的vnode。这节省了删除和添加dom的开销。现在的问题是,我的示例恰好具有相同数量的新旧vnode。如果它们不同,就会有问题。把例子改成这样:把实现思路改成:先看旧的,新的长,还是新的长。如果旧的长,我就遍历新的,然后删除多余的旧节点。如果新的长,我会遍历旧的,然后添加多余的新vnode。还有优化的空间,如下图所示:通过我们上面的diff算法,实现过程会比较prevevnode和nextvnode,如果tags相同,只比较vnodedata和children。发现标签的子节点(文本节点a、b、c)不同,于是分别删除文本节点a、b、c,然后重新生成新的文本节点c、b、a。但实际上这些?只是在不同的位置,所以优化的方案是复用生成的dom,移动到正确的位置。怎么搬?我们使用密钥将新旧vnode映射一次。首先,我们找到一个可以重复使用的vnode。我们可以做两次遍历,外层遍历nextvnode,内层遍历prevvnode。如果只移动下一个vnode和上一个vnode,则vnodedata和children没有变化。调用patchVnodedom操作后不会有任何变化。接下来,只需要将具有相同key的vnode移动到正确的位置即可。我们的问题变成了如何移动。首先你需要知道两件事:每个prevvnode引用一个真实的dom节点,每个nextvnode此时都没有真实的dom节点。调用patchVnode时,会将prevVnode引用的真实Dom的引用赋值给nextVnode,像这样:还是拿上面的例子,外层遍历下一个vnode时,遍历第一个元素时,第一个vnode是li?,然后去prevvnode找,在最后一个节点找。这里,外层是第一个元素,没有任何移动操作。我们记录下这个vnode在prevVnode中的索引位置lastIndex。接下来在遍历的时候,如果j=lastIndex时,表示顺序正确,无需移动,则lastIndex=j;也就是说,只有prevVnode后面的元素向前移动,原来的顺序是正确的,保持不变。现在我们的diff代码变成了这样:同样的问题,如果新旧vnode的元素个数相同,已经可以工作了。接下来要做的是添加节点和删除节点。首先是添加新节点。整个框架中调用patch函数将vnode挂载到真实dom中,patch中调用createElm生成真实dom。按照上面的实现,如果nextVnode中有一个节点不在prevVnode中,就会出现问题:li(d)在prevVnode中找不到,这时我们需要调用createElm挂在这个新节点上,因为节点这里需要超过li(b)和li?之间的Insert,所以需要使用insertBefore()。每次遍历nextVnode时,用一个变量find=false表示是否能在prevVnode中找到该节点,找到则find=true。如果内部遍历后find为false,说明这是一个新节点。我们的createElm函数需要判断第四个参数。如果不是,则使用appendChild直接将元素放在父节点的末尾。如果有第四个参数,我们需要调用insertBefore将其插入到正确的位置。接下来要做的就是删除prevVnode的冗余节点:nextVnode中没有li(d),我们需要在执行完上述所有过程后遍历一次prevVnode,然后在nextVnode中查找,如果找不到找到相同key的节点,就说明该节点已经被删除了,我们直接使用removeChild方法来删除Dom。完整代码:https://github.com/TingYinHelen/tempo/blob/main/src/platforms/web/patch.js在react-diff分支(可能代码仓库还没有开源,等我实现一个更完美的吧会不定期开源的,项目结构可能会变,看tempo仓库??就好。)我这里代码实现的diff算法,很明显时间复杂度是O(n2).那么在算法上还有优化的空间。这里我将nextChildren和prevChildren设计为数组类型。这里nextChildren和prevChildren可以设计成对象类型。用户传入的key作为对象的key,vnode作为对象的value,这样就只能循环nextChildren,然后通过prevChildren[key]找到prevChildren中可复用的dom。这将时间复杂度降低到O(n)。以上就是react的diff算法的实现。Vue的diff算法先说上面代码的问题。比如下面这种情况:如果按照react的方法,整个过程会移动两次:li?是第一个节点,不需要移动,lastIndex=2li(b),j=1,jchild.key===newStartVnode.key);如果(idxInOld>=0){elm.insertBefore(prevChildren[idxInOld].elm,oldStartVnode.elm);prevChildren[idxInOld]=未定义;新的开始索引++;}}}}接下来是添加新的节点:这个Arrangement方法,按照上面的方法,经过1,2,3,4比较,找不到相同的key,然后使用newStartIndex在旧的vnode中查找,但仍然找不到。这时候就说明是新节点了。它被插入到oldStartIndex前面,最后是删除节点。我把它作为课后作业,同学们可以自己实现最终的删除算法。完整代码在https://github.com/TingYinHelen/tempo的vue分支。附言。本文部分内容引用自《比对一下react,vue2.x,vue3.x的diff算法》。本文分享自华为云社区《90后小姐姐带你了解Vue3.0新特性》,原作者:技术火炬手。点击关注,第一时间了解华为云的新鲜技术~