作为一个Vue的重度用户,我从Vue 2.0时代就一直在用Vue做项目,大大小小的项目做了十几个,也算是积累了一些经验。从2017年开始,就不断有Vue 3.0的消息传出来,说Vue 3.0会重写核心,会有很多激动人心的新特性,性能会有大幅提升。作为一个Vue粉,我一直非常期待Vue 3.0的到来。

2018年初,Vue 3.0的设计思路和新特性慢慢浮出水面,Evan You(尤雨溪)也在各种场合分享了Vue 3.0的进展。我按捺不住好奇心,开始尝试学习和体验Vue 3.0的新特性。但是这个过程并不顺利,从最开始的兴奋期待,到中间的困惑迷茫,再到最后的"从入门到放弃",我经历了很多。

今天就来记录一下我学习Vue 3.0前瞻的真实经历,包括Vue 3.0有哪些新特性、我遇到了哪些坑、为什么说"从入门到放弃",以及对Vue 3.0的一些思考和期待。

一、为什么对Vue 3.0这么期待

先说说我为什么对Vue 3.0这么期待。

我用Vue 2.0做了很多项目,总体来说体验非常好。Vue的学习曲线平缓,文档友好,生态完善,开发效率很高。但是用久了之后,也发现了一些Vue 2.0的痛点和局限。

痛点一:响应式系统的局限

Vue 2.0的响应式系统是基于Object.defineProperty实现的。这个方案有一些固有的局限:

  • 不能检测对象属性的添加和删除,需要用Vue.set和Vue.delete来处理。
  • 不能检测数组索引的直接修改和长度变化,需要用Vue.set或者数组的变异方法。
  • 初始化时需要递归遍历整个对象,把所有属性都转换成getter/setter,对于大型对象,初始化的性能开销比较大。

这些问题在日常开发中经常遇到,虽然有解决方案,但是总是觉得不够优雅。特别是做大型项目的时候,对象嵌套很深,数据量很大,响应式初始化的性能问题就比较明显了。

痛点二:大型组件的逻辑复用困难

在Vue 2.0中,组件逻辑复用主要靠mixins。mixins虽然能实现逻辑复用,但是有很多问题:

  • 命名冲突:多个mixins可能有同名的属性或方法,会互相覆盖。
  • 来源不清晰:组件用了多个mixins之后,很难搞清楚某个属性或方法来自哪个mixin。
  • 配置合并复杂:mixins的合并规则比较复杂,特别是生命周期钩子、data、methods等的合并,容易出问题。

做大型项目的时候,组件越来越复杂,逻辑越来越多,mixins的这些问题就越来越突出。我曾经做过一个项目,一个组件用了五六个mixins,出了bug之后根本搞不清楚是哪个mixin的问题,调试起来非常痛苦。

痛点三:TypeScript支持不够好

虽然Vue 2.0也能用TypeScript,但是支持得不够理想。因为Vue 2.0的API是基于选项式的,类型推导比较困难,在TypeScript中使用Vue经常需要写很多类型声明,开发体验不够好。

我们团队最近在考虑引入TypeScript,但是调研了一下Vue 2.0 + TypeScript的方案,发现要么用vue-class-component加装饰器,要么写很多类型断言,都不是很优雅。所以一直在等Vue 3.0对TypeScript有更好的支持。

痛点四:打包体积偏大

Vue 2.0的核心库打包之后(gzip)大概有30多KB,虽然不算大,但是对于一些对体积敏感的场景(比如移动端、轻量级应用),还是希望能更小一些。而且Vue 2.0中很多功能是全局挂载的,即使你没有用到,也会被打包进去,不能按需加载。

正是因为有这些痛点,我对Vue 3.0充满了期待。听说Vue 3.0会用Proxy重写响应式系统,会引入新的逻辑复用方式,会用TypeScript重写,会有更好的Tree Shaking支持,每一条都正中我的痛点。所以一有Vue 3.0的消息,我就迫不及待地去了解和学习。

二、Vue 3.0有哪些让人兴奋的新特性

2018年初,Evan You在各种分享中透露了Vue 3.0的设计思路和新特性。我整理了一下,主要有以下几个方面,每一个都让人非常兴奋。

新特性一:基于Proxy的响应式系统

这是Vue 3.0最核心的改进。Vue 3.0将用ES6的Proxy来替代Object.defineProperty实现响应式系统。

Proxy的好处是显而易见的:

  • 可以检测对象属性的添加和删除,不再需要Vue.set和Vue.delete。
  • 可以检测数组的索引修改和长度变化,数组的响应式问题彻底解决。
  • 不需要递归遍历整个对象来初始化响应式,只有在属性被访问时才会进行响应式转换,初始化性能大幅提升。
  • 支持Map、Set、WeakMap、WeakSet等新的数据结构。

根据Evan You分享的benchmark数据,Vue 3.0的响应式初始化速度比Vue 2.0快了很多,特别是在处理大型对象的时候,性能提升非常明显。而且内存使用也减少了,因为不需要预先把所有属性都转换成getter/setter。

这个改进对我来说是最期待的,因为它解决了Vue 2.0响应式系统的所有痛点,让响应式变得更加自然和高效。

新特性二:基于函数的API(Composition API)

这是Vue 3.0另一个重要的改进,也是争议最大的一个改进。Vue 3.0将引入一种新的基于函数的API(当时叫Function-based API,后来正式命名为Composition API),用来解决组件逻辑复用的问题。

基于函数的API的核心思想是:把组件的逻辑封装成函数,通过函数调用来复用逻辑,而不是用mixins。这种方式的好处是:

  • 没有命名冲突:函数内部的变量是局部的,不会和其他函数冲突。
  • 来源清晰:通过函数调用,可以清楚地知道某个逻辑来自哪个函数。
  • 更好的TypeScript支持:函数式的API天然适合类型推导,TypeScript体验会好很多。
  • 更灵活的逻辑组合:可以根据需要自由组合不同的逻辑函数,比mixins灵活得多。

Evan You当时分享了一个例子,用基于函数的API来封装鼠标位置跟踪的逻辑:

function useMouse() {
  const x = value(0)
  const y = value(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}

然后在组件中使用:

const Component = {
  setup() {
    const { x, y } = useMouse()
    return { x, y }
  },
  template: `<div>{{ x }} {{ y }}</div>`
}

看到这个例子的时候,我觉得非常惊艳。这种方式比mixins优雅太多了,逻辑清晰,来源明确,没有命名冲突,而且TypeScript支持也好。当时我就觉得,这才是组件逻辑复用的正确打开方式。

新特性三:用TypeScript重写,更好的类型支持

Vue 3.0将用TypeScript重写,从底层就对TypeScript有良好的支持。这意味着:

  • Vue的API会有更完善的类型定义,不需要额外的类型声明包。
  • 基于函数的API天然适合类型推导,在组件中使用TypeScript会非常顺畅。
  • IDE的智能提示和代码补全会更加准确,开发体验大幅提升。

对于我们团队来说,这个改进非常重要。我们一直在考虑引入TypeScript,但是Vue 2.0的TypeScript体验不够好,所以一直在等Vue 3.0。Vue 3.0用TypeScript重写之后,引入TypeScript的阻力就小很多了。

新特性四:更好的性能和更小的体积

Vue 3.0在性能和体积上也有很多改进:

  • 响应式系统重写,初始化性能和内存使用都有提升。
  • 虚拟DOM重写,通过静态节点提升、基于Proxy的依赖收集等手段,渲染性能提升。
  • 更好的Tree Shaking支持,更多功能改为按需导入,打包体积更小。

根据Evan You分享的数据,Vue 3.0的运行时性能比Vue 2.0提升了很多,服务端渲染速度提升了2-3倍,打包体积也减小了不少。对于追求性能和轻量的场景来说,这些改进非常有吸引力。

新特性五:其他改进

除了上面这些,Vue 3.0还有一些其他的改进:

  • 支持Fragment(多根节点组件),不再要求组件模板只能有一个根元素。
  • 支持Suspense,处理异步组件的加载状态。
  • 支持Portals(传送门),可以把组件渲染到DOM树之外的地方。
  • 改进的自定义渲染器API,可以更方便地创建自定义渲染器。
  • 新的生命周期钩子命名,更加清晰一致。

每一个改进都让人非常期待,当时我觉得Vue 3.0简直就是完美的,迫不及待地想用上。

三、从入门到放弃:我经历了什么

怀着激动的心情,我开始了Vue 3.0的学习之旅。但是这个过程并没有我想象的那么顺利,我经历了从兴奋到困惑再到放弃的全过程。

阶段一:兴奋入门,到处找资料

刚开始的时候,我非常兴奋,到处找Vue 3.0的资料。但是很快我就发现,Vue 3.0的资料太少了。

因为Vue 3.0还在开发中,没有正式发布,所以没有官方文档,没有完整的教程,没有书籍,只有Evan You在各种分享中的PPT和演讲视频,以及GitHub上的一些设计文档和讨论。

这些资料非常零散,而且很多是英文的,需要自己去整理和理解。我花了好几天时间,把能找到的资料都找了一遍,包括:

  • Evan You在Vue Conf 2018上的演讲视频和PPT
  • GitHub上的vue-next仓库(Vue 3.0的代码仓库)
  • RFC(Request for Comments)文档,里面有Vue 3.0各个特性的设计讨论
  • 各种技术博客和社区讨论

把这些资料整理完之后,我对Vue 3.0的新特性有了一个大概的了解,但是也只是停留在"知道有什么"的层面,具体怎么用、有什么坑,还是不清楚。

阶段二:尝试写Demo,遇到各种问题

光看资料不行,得动手写代码才能真正理解。于是我决定尝试用Vue 3.0写几个Demo。

但是一开始就遇到了问题:Vue 3.0还没有正式发布,怎么安装?

当时Vue 3.0的代码在vue-next仓库里,还没有发布到npm。要体验Vue 3.0,要么自己从源码构建,要么用一些社区提供的实验性版本。我试了好几种方式,折腾了半天,终于把开发环境搭起来了。

搭好环境之后,我开始写第一个Demo,一个简单的计数器。按照基于函数的API的写法,应该是这样的:

import { value, createApp } from 'vue'

const App = {
  setup() {
    const count = value(0)
    const increment = () => {
      count.value++
    }
    return { count, increment }
  },
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button @click="increment">+</button>
    </div>
  `
}

createApp(App).mount('#app')

写的时候就觉得有点别扭,特别是count.value++这个写法。在Vue 2.0中,直接this.count++就可以了,但是在Vue 3.0的基于函数的API中,因为count是一个包装对象(wrapper),需要通过.value来访问和修改实际的值。

这个.value的写法让我很不适应。虽然知道这是为了让基本类型(数字、字符串、布尔值)也能保持响应式,但是写起来确实有点繁琐,而且很容易忘记写.value,导致bug。我写Demo的时候就好几次忘了写.value,调试了半天才发现问题。

写完计数器之后,我又尝试写了一个稍微复杂一点的Demo,包含组件通信、计算属性、生命周期钩子等。这个过程中遇到了更多的问题:

  1. API一直在变:因为Vue 3.0还在开发中,API很不稳定。今天看的文档是一种写法,明天可能就变了。我刚学会一种写法,过几天发现又改了,之前写的代码都不能用了,需要重新学。这种感觉非常挫败。
  1. 文档不完善:很多API没有详细的文档,只有一些设计文档和示例,很多细节不清楚。比如错误处理、边界情况、性能注意事项等,都需要自己去摸索,或者去看源码。对于初学者来说,这非常不友好。
  1. 生态不兼容:Vue 2.0的生态非常完善,有Vuex、Vue Router、Element UI、Vuetify等大量的库和组件库。但是这些库在Vue 3.0中都不能用,需要等它们适配Vue 3.0。当时Vue 3.0还没发布,这些库自然也没有适配版本,所以用Vue 3.0写项目的时候,很多常用的功能都要自己实现,非常不方便。
  1. 工具链支持不好:Vue 2.0有非常完善的工具链支持,比如Vue DevTools、Vetur、vue-cli等。但是Vue 3.0因为API变了,这些工具都需要更新才能支持。当时这些工具对Vue 3.0的支持都不好,Vue DevTools不能用,Vetur的智能提示不准确,调试非常困难。

写了几个Demo之后,我感觉非常累。每写一个功能都要查半天资料,踩很多坑,而且API还在不断变化,今天写的代码明天可能就不能用了。这种感觉就像是在沙地上盖房子,盖了又塌,塌了又盖,永远看不到成品。

阶段三:困惑迷茫,开始怀疑人生

写了一段时间Demo之后,我开始感到困惑和迷茫。

最大的困惑是:基于函数的API真的比选项式API好吗?

Vue 2.0的选项式API(data、methods、computed、watch等)虽然有逻辑复用的问题,但是它的优点是结构清晰,新手容易上手。一个组件的各个部分分门别类,data是数据,methods是方法,computed是计算属性,一目了然。

而基于函数的API,所有的逻辑都写在setup函数里,虽然逻辑可以封装成函数复用,但是如果组件比较复杂,setup函数可能会变得很长,各种变量和函数混在一起,结构反而不如选项式API清晰。

而且,.value的写法确实很不优雅,也容易出错。虽然Evan You解释说这是为了保持基本类型的响应式,是必要的权衡,但是作为一个使用者,我还是觉得写起来很别扭。

还有一个困惑是:Vue 3.0的学习成本是不是太高了?

Vue一直以"简单易学"著称,这也是它能快速流行的重要原因。但是Vue 3.0引入了很多新的概念和API,基于函数的API、响应式包装对象、新的生命周期钩子等,学习成本比Vue 2.0高了不少。

对于新手来说,Vue 3.0可能不再那么友好了。他们不仅要学习Vue的基本概念,还要理解响应式的原理、函数式API的设计思想、.value的机制等。这会不会让Vue失去"简单易学"的优势?

这些困惑让我开始怀疑,Vue 3.0是不是真的像我想象的那么好。我甚至开始觉得,Vue 2.0其实已经够用了,那些痛点虽然存在,但是也不是不能忍受,有没有必要花这么大的成本去升级到Vue 3.0?

阶段四:从入门到放弃,决定等正式版

在困惑和迷茫中挣扎了一段时间之后,我终于决定:暂时放弃学习Vue 3.0,等正式版发布之后再说。

做出这个决定的原因主要有几个:

  1. API还不稳定:Vue 3.0还在开发中,API一直在变,现在学了之后可能很快就过时了。与其在不稳定的版本上浪费时间,不如等正式版发布、API稳定之后再学。
  1. 生态还不成熟:Vue 3.0的生态还没有建立起来,常用的库和工具都没有适配,现在学了也没法在实际项目中用。学了不用,很快就会忘。
  1. 学习成本太高:在没有完善文档和教程的情况下,学习Vue 3.0的成本太高了,需要自己去摸索,踩很多坑。有这个时间,不如把Vue 2.0用得更熟练,或者学习其他更稳定的技术。
  1. Vue 2.0还能用:Vue 2.0已经非常成熟和稳定了,生态完善,工具链齐全,完全能满足目前的项目需求。没有必要急着升级到Vue 3.0。

做出这个决定之后,我反而觉得轻松了很多。不再每天追Vue 3.0的消息,不再折腾那些不稳定的API,不再为那些还没确定的设计而困惑。我把精力重新放回到Vue 2.0的项目上,把现有的项目做好,同时关注Vue 3.0的进展,等正式版发布之后再系统地学习。

这就是我的"Vue 3.0前瞻从入门到放弃"的经历。

四、从入门到放弃,我学到了什么

虽然最后"放弃"了,但是这段学习经历并不是完全没有收获的。我从中学到了很多东西。

收获一:对Vue的设计思想有了更深的理解

通过学习Vue 3.0的设计思路和新特性,我对Vue的设计思想有了更深的理解。我明白了Vue 2.0的响应式系统为什么是那样设计的,它有什么局限,Vue 3.0为什么要用Proxy重写。我也明白了组件逻辑复用的难点在哪里,基于函数的API是怎么解决这些问题的。

这些理解不仅有助于我更好地使用Vue 2.0,也让我对前端框架的设计有了更深入的认识。一个好的框架,不仅要功能强大,还要API设计优雅,学习曲线合理,生态完善。Vue 3.0的设计过程,让我看到了一个优秀的框架是如何权衡各种因素、不断演进的。

收获二:学会了用批判性的眼光看待新技术

以前我总是对新技术充满盲目崇拜,觉得新的就是好的,最新版本的一定比旧版本强。但是通过学习Vue 3.0的经历,我学会了用批判性的眼光看待新技术。

新技术不一定就比旧技术好,它可能解决了旧技术的一些问题,但是也可能引入新的问题。比如Vue 3.0的基于函数的API解决了mixins的问题,但是也带来了.value的不优雅和学习成本的增加。Proxy解决了Object.defineProperty的局限,但是也带来了浏览器兼容性的问题(虽然现代浏览器都支持Proxy,但是如果要兼容IE的话就不行了)。

所以,面对新技术,我们不应该盲目跟风,而应该理性地分析:它解决了什么问题?带来了什么新问题?升级的成本和收益是什么?现在是不是升级的好时机?想清楚这些问题之后,再决定要不要学、要不要用。

收获三:明白了"时机"的重要性

学习新技术,时机很重要。太早了,技术还不稳定,文档不完善,生态不成熟,学习成本高,学了也用不上;太晚了,别人都已经用上了,你还在学基础,就落后了。

最好的时机是技术刚刚稳定、生态开始成熟、有了比较完善的文档和教程的时候。这时候学习,成本比较低,而且学了之后很快就能在实际项目中用上,学用结合,掌握得更牢固。

Vue 3.0在2018年初的时候,显然还太早了,API都还没稳定。这时候去学,事倍功半。等它正式发布、API稳定、生态开始成熟之后再学,才是更好的时机。

这个道理不仅适用于Vue 3.0,也适用于所有的新技术。不要做最早的"吃螃蟹的人",除非你有特别的需求或者特别的兴趣。对大多数人来说,等技术稍微成熟一点再学,是更明智的选择。

收获四:夯实基础比追新更重要

在折腾Vue 3.0的过程中,我发现很多困惑其实是因为基础不够扎实。比如对响应式原理的理解不够深入,对JavaScript的Proxy、Reflect等新特性不够熟悉,对函数式编程的思想不够了解。如果这些基础足够扎实,学习Vue 3.0的新特性就会容易很多。

这让我意识到,夯实基础比追新更重要。框架和库会不断更新,今天学Vue 3.0,明天可能又有新的框架出来。但是基础的东西是不变的,比如JavaScript的核心概念、数据结构和算法、设计模式、浏览器原理、网络协议等。把这些基础打扎实了,不管框架怎么变,都能快速上手。

所以,从那之后,我不再花太多时间去追那些还不稳定的新技术,而是把更多的精力放在夯实基础上。基础扎实了,学什么都快。

五、对Vue 3.0的期待和建议

虽然我暂时"放弃"了学习Vue 3.0,但是我还是非常期待它的正式发布。作为一个Vue的重度用户,我希望Vue 3.0能越来越好。

期待一:API稳定之后,能有完善的文档和教程

文档和教程对新技术的推广非常重要。Vue 2.0的文档非常友好,这也是Vue能快速流行的重要原因。希望Vue 3.0正式发布之后,能有同样高质量的文档和教程,让新手也能快速上手。

特别是基于函数的API,是一个比较新的概念,很多人可能不太习惯。希望能有详细的指南和最佳实践,告诉大家怎么组织代码、怎么复用逻辑、怎么处理大型组件,让大家能更快地接受和使用新的API。

期待二:生态能尽快跟上

框架的生态非常重要。Vue 2.0的成功,很大程度上得益于它完善的生态。希望Vue 3.0正式发布之后,Vuex、Vue Router、Element UI等核心库能尽快适配,第三方组件库和工具也能尽快跟上,让大家能在实际项目中放心地使用Vue 3.0。

期待三:能有平滑的升级路径

从Vue 2.0升级到Vue 3.0,肯定会有一些breaking changes。希望Vue团队能提供一个平滑的升级路径,比如迁移工具、兼容层、详细的迁移指南等,让大家能比较顺利地从Vue 2.0升级到Vue 3.0,而不是必须重写整个项目。

期待四:保持Vue"简单易学"的优势

最后,也是最重要的一点,希望Vue 3.0能保持Vue"简单易学"的优势。虽然引入了很多新特性,但是希望不要让学习曲线变得太陡峭。新特性可以有,但是最好能有渐进式的学习路径,新手可以先用简单的方式上手,然后再慢慢学习高级特性。不要让Vue变成一个只有专家才能用的框架。

六、结语

我的"Vue 3.0前瞻从入门到放弃"的经历,到这里就讲完了。

回顾这段经历,我觉得"放弃"并不是一件坏事。在不合适的时机,选择暂时放弃,把精力放在更重要的事情上,是一种明智的选择。这不代表我不喜欢Vue 3.0了,也不代表我以后不会学了,只是我觉得现在还不是最好的时机。

等Vue 3.0正式发布、API稳定、生态成熟之后,我一定会重新开始学习,并且把它用到实际项目中。因为我相信,Vue 3.0会是一个非常优秀的框架,它会让前端开发变得更高效、更优雅。

最后想说的是,技术在不断发展,新的框架和库层出不穷。作为开发者,我们要保持学习的热情,但是也要有自己的判断,不要盲目跟风。选择适合自己的技术,在合适的时机学习,把基础打扎实,才是最重要的。

Vue 3.0,我们正式版见。