大家好,我们是明源云链的前端团队。在接下来的日子里,我们会继续分享公司的技术积累,关注我们,不要迷路!一、Vue框架的演变Vue.js从1.x版本到2.0版本,最大的升级就是引入了虚拟DOM的概念,为后续的服务端渲染和跨端框架Weex提供了基础。Vue2.x已经发展了很长时间,期间各个生态都得到了完善,也有很多优秀的项目案例。对于使用Vue2.x的开发者来说,已经可以满足日常开发的所有需求。你可能认为这是一个非常好的框架,但是在做对小对的眼里,还是有一些不足之处。从Vue2.x到Vue3.0,解决了之前存在的一些痛点,比如源码本身的可维护性,数据量大带来的渲染和更新的性能问题,还有一些鸡肋的API想放弃但为了兼容一直保留着;此外,作者也希望能给开发者带来更好的编程体验,比如更好的TypeScript支持、更好的逻辑复用实践等,因此希望从源代码、性能和语法API框架三大方面进行优化。二、Vue3.0的优化点1、源码优化源码的优化主要体现在使用monorepo和TypeScript来管理和开发源码。这样做的目的是提高自身代码的可维护性。Vue2.x的项目组织,代码托管在src目录下,分为以下几个模块。src|-compiler#模板编译相关代码|-core#平台无关的通用运行时代码|-platforms#平台特定代码|-server#服务端渲染相关代码|-sfc#.vue单文件解析相关代码|-shared#共享工具代码Vue3.0采用monorepo的项目管理方式,将代码托管在packages目录下。每个模块代码都有单一的职责,并有自己的API、类型定义和完整的测试用例。是不是让代码维护更方便?单独引用vue的响应式功能也比较方便。可以直接导入reactivitymodule而不用直接导入整个包,这样可以减少包的大小。这在Vue2.x上是不可能的。packages|-compiler-core|-compiler-dom|-compiler-sfc|-compiler-ssr|-reactivity|-runtime-core|-runtime-dom|-shared|-vue|-...2.更好的类型支持:TypescriptVue3.0使用typescript重构整个项目,对类型支持更加友好。使用类型化语言非常有利于代码维护。可以避免开发阶段很多不必要的错误,也可以利用IDE的类型推导能力帮助我们更好的识别参数类型。Vue2.x中使用了Flow。首先,Flow是Facebook出品的javascript静态类型检测工具。它可以以极小的成本迁移现有的javascript代码,并且非常灵活。但是由于迭代的过程,发现有些比较复杂的类型不能很好的支持。比如我们在阅读Vue2.x的源码时,可以在代码注释中看到对Flow的吐槽:constpropOptions:any=vm.$options.props;//wtf流?为什么会这样?这是因为FLow没有正确推断出vm.$options.props的类型,开发者不得不强制将propOptions类型设置为any,这似乎不合理。Vue3中的所有源代码都使用Typescript进行了重构。Typescript更有利于复杂应用场景的代码维护。就目前的生态而言,TypeScript团队越来越好,支持的功能越来越多,也能更好的弥补js的缺陷。3.性能优化性能优化主要体现在以下几点:1.源代码大小优化2.数据劫持优化3.编译优化4.语法API优化3.1源代码大小优化Vue3在降低源代码大小方面做了哪些工作?去除一些冷门特性(如filter、inline-template等),引入tree-shaking技术,减少打包体积。3.2数据劫持优化Vue.js与React不同的是它的数据是响应式的。Vue.js1.x版本就已经有了这个特性,这也是为什么Vue.js爱好者喜欢Vue.js的原因之一就是DOM是数据的映射。DOM可以在数据变化后自动更新。用户只需要专注于数据修改,没有任何其他的精神负担。3.3编译优化Vue2.xVSVue3.0首先渲染对比,可以看出Vue3.0在渲染上有明显优势,占用内存少,渲染速度更快。同样的,下面节点的渲染也在diff阶段进行了优化。我们知道,通过数据劫持和依赖收集,Vue2的数据更新和触发重新渲染的粒度是在组件级别的,虽然Vue可以保证更新组件的触发最小化,但是仍然需要遍历组件内部的整个vnode树单个组件。但是,我们有很多静态节点而只有一个动态节点。我们希望当文本发生变化时,只更新动态节点的diff,避免很多不必要的diff。但是,这在Vue2中是不可能的。因此在Vue3中重写了diff算法,增加了属性标签,在编译阶段通过对静态模板的解析来编译生成Block树。块树是嵌套块,根据动态节点指令切割模板。每个区块内部的节点结构是固定的,每个区块只需要使用一个Array来跟踪它所包含的动态节点。编译完以下代码后,我们等待编译后的代码。这里推荐一个vue3模板编译网站
一个静态节点子节点
一个静态节点子节点
静态节点动态节点{{text}}
编译后:import{createVNodeas_createVNode,createTextVNode作为_createTextVNode,vShow作为_vShow,withDirectives作为_withDirectives,toDisplayString作为_toDisplayString,openBlock作为_openBlock,createBlock作为_createBlock,}来自“vue”;导出函数,render_ca(_ct$props,$setup,$data,$options){return(_openBlock(),_createBlock("div",null,[_createVNode("p",null,[_createTextVNode("静态节点"),_createVNode("span",{onClick:_ctx.handleClick},"子节点",8/*PROPS*/,["onClick"]),]),_createVNode("p",null,[_createTextVNode("静态节点"),_createVNode("span",null,"子节点"),]),_withDirectives(_createVNode("p",null,"静态节点",512/*NEED_PATCH*/),[[_vShow,_ctx.show]]),_createVNode("p",null,"DynamicNode"+_toDisplayString(_ctx.text),1/*TEXT*/),]));}//从上面检查AST的控制台It可以看出_createVNode接受四个参数,第四个参数是编译后的模板类型,编译过程中会进行diff。如果没有传递第四个参数,它将被忽略。试想一下,如果一个组件中的嵌套层次比较深,而且多为静态节点,这将是一个多么大的性能提升。3.4语法API优化:CompisitionAPI源码优化逻辑组织Vue3.0在逻辑复用上有强大的功能。相比Vue2.x的mixins语法,更利于维护和管理,尤其是在大型项目的应用中。可以更好的支持逻辑封装和复用。以上是Vue作者重构Vue-cliUI的一个例子,是Vue-cliUI应用中一个复杂的文件浏览器组件。该组件需要处理许多不同的逻辑问题:跟踪当前文件夹状态并显示其内容处理文件夹导航(如打开、关闭、刷新等)处理新文件的创建切换显示隐藏文件夹处理当前文件的修改directory我们通过颜色区分逻辑关注点,每种颜色都是一个相对独立的逻辑封装。上图中左边部分是Vue2.x开发的。我们将Vue2.x中的数据、道具、方法等称为OptionsAPI。相对比较Vue3.0中的CompositionAPI。结果很明显,左边的OptionsAPI里面有两个紫色的区域,这段代码的部分逻辑是分散的,这会导致我们开发过程中任何一个地方的变化都会引起另一边的变化,变化一个穴位需要反复上下跳动,很不舒服。使用CompositionAPI,色块分明,逻辑独立。当代码比较多的时候,我们也可以灵活的将它们提取出来,对于后期的维护非常方便。优化逻辑重用让我们回顾一下我们在Vue2.x中是如何重用逻辑的。我们将使用mixins。举一个鼠标位置监听的经典例子,我们会写如下函数:constmousePositionMixin={data(){return{x:0,y:0,};},安装(){窗口。addEventListener("mousemove",this.update);},destroyed(){窗口。removeEventListener("mousemove",this.update);},方法:{更新(e){this.x=e.pageX;this.y=e.pageY;},},};导出默认的mousePositionMixin;然后在组件中使用:
这似乎很好用,结构看起来很清楚,但是当我们的组件越来越大时,就会出现一些问题。首先,每个mixin都可以定义自己的props和data,它们之间没有任何意义,所以很容易定义相同的变量,造成命名冲突。另外,对于组件来说,如果在模板中使用了当前组件中没有定义的变量,就不容易知道这些变量是在哪里定义的,也就意味着数据来源不明确。接下来我们看看Vue3.0的CompositionAPI是如何实现的;从“vue”导入{onMounted,onUnmounted,ref};函数useMouse(){constx=ref(0);常量y=ref(0);constupdate=(e)=>{x.value=e.pageX;y.value=e.pageY;};onMounted(()=>{window.addEventListener("mousemove",update);});onUnmounted(()=>{window.removeEventListener("mousemove",update);});返回{x,y}};导出默认useMouse;这里我们使用ReactHooks的命名规范,定义一个useMouse,然后在组件中使用:
可以看出使用CompositionAPI写入的数据源是清晰,再多Hooks也不怕冲突。Vue3设计的CompositionAPI帮助我们很好的解决了mixins的这两个问题。CompositionAPI虽然有很多优点,但不代表就没有缺点。我们会在后续的源码分析章节中为大家讲解。另外需要注意的是,CompositionAPI是API的增强,并不是强制使用。如果你的项目需求不多,组件比较简单,你仍然可以使用Vue3中的OptionsAPI来开发你的项目。LanguageVue3.0尚未发布正式版,预计今年推出,但Vue3.0的文档已经发布。有兴趣的可以去Vue3官网(https://v3.vuejs.org/)查看。最后要说的是,Vue.js3.0是使用ES2015语法开发的。Proxy等一些API没有polyfills,这意味着官方需要单独发布一个IE11兼容版本来支持IE11。如果你的项目需要兼容IE11,你就得小心使用某些API,这会带来一些额外的精神负担。下一篇预览:Vue3预览-新的运行机制最后一点如果你已经看到了,希望你在离开之前还喜欢它~明源云链技术团队长期招募优秀的合作伙伴,欢迎有兴趣的小伙伴前来咨询~