尤雨溪自述:打造Vue 3背后的故事
希望能以此文帮助你更好地了解 Vue 3 背后的故事。...
作者 | 尤雨溪
译者 | 王强
编辑 | 蔡芳芳
尤雨溪在今年年初 Vue 3 正式发布之前撰写了这篇长文,详述 Vue 3 的设计过程。前端之巅将全文翻译如下,希望能帮助你更好地了解 Vue 3 背后的故事。
在过去的一年中,Vue 团队一直都在开发 Vue.js 的下一个主要版本,我们希望能在今年上半年发布它(本文完成时这项工作尚在进行)。Vue 新版本的理念成型于 2018 年末,当时 Vue 2 的代码库已经有两岁半了。比起通用软件的生命周期来这好像也没那么久,但在这段时期,前端世界已经今昔非比了。
在我们更新(和重写)Vue 的主要版本时,主要考虑两点因素:首先是新的 JavaScript 语言特性在主流浏览器中的受支持水平;其次是当前代码库中随时间推移而逐渐暴露出来的一些设计和架构问题。
为什么重写
利用新的语言特性
随着 ES2015 标准的落地,JavaScript(以前被称为 ECMAScript,缩写为 ES)获得了诸多重大改进,同时主流浏览器也终于开始对这些新特性提供良好的支持了。其中的一些特性使我们能够大幅提升 Vue 的能力。
这里面最值得一提的就是 Proxy:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
它为框架提供了拦截针对对象的操作的能力。Vue 的一项核心特性就是监听用户定义状态的变化,并响应式更新 DOM。Vue 2 是通过替换状态对象属性的 getter 和 setter 来实现这种响应能力的。转向 Proxy 后,我们就能解决 Vue 当下存在的诸多局限(比如无法检测新增属性等),还能提供更好的性能。
但 Proxy 是一个原生的语言特性,无法在老式浏览器中提供完整的 polyfill。为此我们需要改动新版框架的浏览器支持范围——这是一项破坏性变更,只有新的主要版本才能实现。
解决架构问题
在维护 Vue 2 的过程中,我们积累的很多问题受限于现有的架构是很难解决的。例如,模板编译器的写法使我们很难实现良好的源映射支持。另外,虽然 Vue 2 技术上支持构建以非 DOM 平台为目标的高级渲染器,但为了实现这一支持,我们需要 fork 代码库,还得复制一大堆代码。在现有代码库上修复这些问题需要大量高风险的重构工作,几乎已经等同于重写了。在现有代码库上修复这些问题需要大量高风险的重构工作,几乎等同于重写了。
同时,我们在很多内部模块与看起来无处可去的零散代码之间生成了很多隐藏的耦合关系,结果积累了不少技术债。现在我们很难单独理解代码库中某一部分的含义,而且我们也注意到贡献者们很少有信心做出突破性的更改。通过重写,我们得以基于这些问题重新思考代码的组织方式。
早期的原型阶段
转向 TypeScript
我们还注意到越来越多的用户在结合使用 Vue 和 TypeScript。为了支持他们的使用场景,我们需要在源码之外单独编写和维护一套 TypeScript 声明,其使用了另一套类型系统。转向 TypeScript 后,我们就能自动生成声明文件,降低维护成本。
解耦内部包
制定 RFC 流程
我们知道这一步要在早期谨慎进行。Vue 的广泛流行意味着重大更改可能会给用户带来巨大的迁移成本,还可能让生态碎片化。为了让用户对重大更改提交反馈,我们在 2019 年初制定了一套 RFC(征求意见)流程。
https://github.com/vuejs/rfcs
所有 RFC 都有一个模板,包括动机、设计细节、权衡以及采用策略等内容。这套流程的实现形式,是在一个 Github 仓库上将提案提交成拉取请求,这样自然就可以在评论中讨论提案了。
结果表明这个 RFC 流程非常有用。作为一个思维框架,它强制我们全面考虑一个潜在更改的所有层面,并让整个社区可以参与到设计过程中,并提交经过充分思考的特性需求。
更快,更小
前端框架的性能至关重要。尽管 Vue 2 已经提供了颇具竞争力的性能表现,但这次重写让我们有机会试验新的渲染策略来进一步提升性能。前端框架的性能至关重要。
突破虚拟 DOM 的瓶颈
所幸模板编译这一步让我们可以对模板进行静态分析,并提取动态部分的信息。Vue 2 跳过了静态子树,在一定程度上做到了这一点;但是由于过度简化的编译器架构,更高级的优化就很难实现了。在 Vue 3 中我们重写了编译器,加入了一个合适的 AST transform 管道,让我们能以 transform 插件的形式进行编译时优化。
现在有了新的架构,我们想要找到一个尽可能减少额外开销的渲染策略。一个选项是抛弃虚拟 DOM 并直接生成命令式 DOM 操作,但这会失去直接编写虚拟 DOM 渲染函数的能力,我们发现这是对于高级用户和库作者们非常有价值的能力。此外,这也会是一个影响巨大的重大更改。
接下来的选项就是摆脱不必要的虚拟 DOM 树遍历和属性对比,这也是更新过程中性能开销最大的部分。为此,编译器和运行时需要协同工作:编译器分析模板,生成带有优化线索的代码,而运行时获取线索并选择最快路径。这里有三大优化工作:
首先,在树级别,我们注意到没有动态调整节点结构的模板指令(如 v-if 和 v-for)时,节点的结构完全保持静态。如果我们将模板根据这些结构化指令拆分为一些嵌套"块",每一个块中的节点结构也会保持静态。当我们更新一个块中的节点时,就不必再递归遍历整个树了——块内的动态绑定可以在一个平面数组里追踪。这一优化极大减少了需要遍历的树的数量,规避了大部分虚拟 DOM 树开销。
其次,编译器会激进检测模板中的静态节点、子树甚至数据对象,并在生成的代码中将它们提取到渲染函数之外。这就可以避免在每次渲染时重新创建这些对象,大幅减少了内存占用,并减少了垃圾收集的频率。
最后,在元素级别,编译器会为每一个有动态绑定的元素,根据其需要进行的更新类型生成一个优化标志。比如说一个元素有一个动态的 class 绑定和一些静态属性,它会获得一个标志,表示这里只需要进行 class 检查。运行时会获取这些标志,然后选择最快的路径。
结合这些优化,我们的渲染更新速度获得了显著改进,在某些场景下 Vue 3 的CPU 时间 仅为 Vue 2 的十分之一不到。CPU 时间:是指 JavaScript 运算所消耗的时间,不包括浏览器 DOM 操作所用的时间。
缩小包体积
首先,不是所有人都需要框架的全部功能。例如,从来不需要过渡特性的应用还是需要下载和解析相关代码。
另外,我们在不断给框架增加新特性,框架也在不断变大,没有止境。这样我们在权衡新特性的利弊时,就得非常在意包大小这个权重。结果,我们会倾向于只加入那些大多数用户都会用到的特性。
理想状态下,用户可以在构建时去掉框架中自己不需要的特性(也就是"摇树优化"),只保留自己用到的特性。这样我们在添加只有部分用户会用到的特性时,并不会给其他用户增添应用体积的负担。
在 Vue 3 中,我们把大多数全局 API 和内部 helper 移到了 ES 模块导出中,从而实现了这个目标。这样现代的打包器就可以静态分析模块依赖项,并去掉与未使用导出相关的代码。模板编译器也会生成适合摇树优化的代码,只会对模板确实用到的特性导入 helper。
框架的有些部分是永远无法摇树优化的,因为它们对于所有应用类型来说都很重要。我们将这部分无法舍弃的代码的体积称作基线大小。虽然 Vue 3 增加了很多新特性,但其基线大小只有大约 10KB(gzip 后),不到 Vue 2 的一半。
满足扩展需求
在 Vue 3 的早期设计阶段,我们尝试内置对使用 class 编写组件的支持,从而更好地整合 TypeScript。这里的问题在于,为了让 class 可用而需要的很多语言特性(例如 class fields 和 decorators)都还处在提案阶段,有可能在正式版中出现变化。随之而来的复杂性和不确定性让我们开始质疑 Class API 是否真的合适,因为它只能改善一点 TypeScript 的整合能力而已。
于是我们决定探索其他途径来解决扩展问题。受到 React Hooks 的启发,我们想到了暴露底层的响应式和组件生命周期的 API,从而提供一种更灵活地编写组件逻辑的方式,也就是 Composition API。
https://vue-composition-api-rfc.netlify.com/
Composition API 不再需要用一个长长的配置列表定义组件,它允许用户自由定义、组合和重用组件逻辑,就像写函数一样,同时还能提供完善的 TypeScript 支持。
我们非常喜欢这个想法。尽管 Composition API 是为解决特定类型的问题设计的,但也能用在单纯的组件开发中。在提案的初稿中我们有些忘乎所以,暗示我们可能会在未来的版本中用 Composition API 替换掉当前的 Options API。这引起了社区成员的极大反弹,给我们上了重要的一课,让我们认识到了与社区沟通长期计划和发展方向,以及理解用户需求的重要性。在听取社区反馈之后,我们完全重做了提案,确认 Composition API 只是锦上添花,是 Options API 的补充。新版提案的反馈要正面许多,我们还收到了很多建设性的意见。
把握平衡
如今有超过一百万的开发人员在使用 Vue,其中有只懂一点 HTML/CSS 的新手,从 jQuery 一路走来的专家,从其他框架迁移过来的老鸟,在寻找前端解决方案的后端工程师,还有负责设计大规模软件的架构师。开发人员的多样性意味着使用场景的多样性:有的开发人员可能想要提升旧项目的交互体验,另一些人可能想要快速开发低成本的一次性项目;架构师可能要应对规模巨大的长期项目,以及项目生命周期内的开发团队成员变动。开发人员的多样性意味着使用场景的多样性。
Vue 的设计在不断根据这些需求变化和发展,我们也设法从诸多权衡中找到平衡点。Vue 的口号“渐进式框架”,背后就是这个过程中形成的分层 API 设计。新手可以通过 CDN script、基于 HTML 的模板以及直观的 Options API 顺利学习入门。而专家可以通过全功能的 CLI、渲染函数以及 Composition API 来处理复杂需求。
要实现我们的愿景还有很多工作要做,其中最重要的就是更新支持库、文档和工具,以保证平滑的迁移。我们会在未来的几个月中继续努力,而且我们迫不及待想要看到社区能用 Vue 3 创造怎样的精彩了。
作者介绍
尤雨溪是 Vue.js 框架的创建者和项目领导,也是一位独立开源开发者。
原文链接
https://increment.com/frontend/making-vue-3/
活动推荐
GMTC 全球大前端技术大会(北京站)2020 关注前端、移动、AI 应用等多个技术领域,大会聚焦前沿技术及实践经验,旨在帮助参会者了解大前端 & 移动开发领域的技术趋势与实践案例。大会目前 8 折优惠报名,限时立减 960 元!了解更多大会内容,可扫描下图二维码或点击【阅读原文】。联系票务经理鱼丸:13269078023(同微信)
关注 前端之巅
微信扫一扫关注公众号