Vue.js从1.x到2.0版本,最大的升级就是引入了虚拟DOM的概念,它为后续做服务端渲染以及跨端框架Weex提供了基础。 Vue.js 2.0的不足之处: 1. 源码自身的维护性 数据量大后带来的渲染和更新的性能问题 2. 兼容性 为了兼容一直保留的鸡肋API
对于Vue.js框架本身开发的优化,它的目的事让代码更易于开发和维护。 源码的优化主要体现在使用monorepo和TypeScript管理和开发源码,这样做的目的是提升自身代码可维护性
依赖ES2015模块语法的静态结构(即import和export),通过编译阶段的静态分析,找到没有引入的模块并打上标记 Eg: math.js
export function square(x) { return x * x } export function cube(x) { return x * x * x }我们在这个模块外面只引入了cube方法:
import { cube } from './math.js'最终math模块会被webpack打包生成如下代码:
/* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { 'use strict'; /* unused harmony export square */ /* harmony export (immutable) */ __webpack_exports__['a'] = cube; function square(x) { return x * x; } function cube(x) { return x * x * x; } });可以看到,未被引入的square模块被标记了, 然后压缩阶段会利用例如uglify-js、terser 等压缩工具真正地删除这些没有用到的代码。 也就是说,利用tree-shaking 技术,如果你在项目中没有引入Transition、KeepAlive 等组件,那么它们对应的代码就不会打包,这样也就间接达到了减少项目引入的Vue.js包体积的目的。
Vue.js 1.x和Vue.js 2.x内部都是通过 Object.defineProperty这个API去劫持数据的getter和setter,具体是这样的:
Object.defineProperty(data, 'a',{ get(){ // track }, set(){ // trigger } })但这个API有一些缺陷,它必须预先知道要拦截的key 是什么,所以它并不能检测对象属性的添加和删除。尽管Vue.js 为了解决这个问题提供了$set和$delete 实例方法,但是对于用户来说,还是增加了一定的心智负担。 另外Object.defineProperty 的方式还有一个问题,举个例子,比如这个嵌套层级比较深的对象:
export default { data: { A: { B: { C: { D: 1 } } } } }由于Vue.js 无法判断你在运行时到底会访问到哪个属性,所以对于这样一个嵌套层级较深的对象,如果要劫持它内部深层次的对象变化,就需要递归遍历这个对象,执行Object.defineProperty 把每一层对象数据都变成响应式的。毫无疑问,如果定义的响应式数据过于复杂,这就会有相当大的性能负担。 为了解决上述 2 个问题,Vue.js 3.0使用了Proxy API做数据劫持,它的内部是这样的:
observed = new Proxy(data, { get() { // track }, set() { // trigger } })由于它劫持的是整个对象,那么自然对于对象的属性的增加和删除都能检测到。 但要注意的是,Proxy API 并不能监听到内部深层次的对象变化,因此 Vue.js 3.0 的处理方式是在getter中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归,这样无疑也在很大程度上提升了性能
除了数据劫持部分的优化,我们可以在耗时相对较多的patch阶段想办法,通过在编译阶段优化编译的结果,实现运行时patch过程的优化。Vue.js 2.x的数据更新并触发重新渲染的粒度是组件级的;
除此之外,Vue.js 3.0 在编译阶段还包含了对Slot 的编译优化、事件侦听函数的缓存优化,并且在运行时重写了diff算法
在Vue.js 1.x和2.x版本中,编些组件本质就是在编写一个‘包含了描述组件选项的对象’,称为Options API
在Vue.js 3.0中,提供了一中新的API:Composition API,就是将某个关注点相关的代码全都放在一个函数里面,这样当需要修改一个功能时,就不再需要在文件中跳来跳去
当开发项目变得复杂的时候,免不了需要抽象出一些复用的逻辑。 在Vue.js 2.x中,我们通常会用 mixins 去复用逻辑,举一个鼠标位置侦听的例子,我们会编写如下函数 mousePositionMixin:
const mousePositionMixin = { data() { return { x: 0, y: 0 } }, mounted() { window.addEventListener('mousemove', this.update) }, destroyed() { window.removeEventListener('mousemove', this.update) }, methods: { update(e) { this.x = e.pageX this.y = e.pageY } } } export default mousePositionMixin然后在组件中使用:
<template> <div> Mouse position: x {{ x }} / y {{ y }} </div> </template> <script> import mousePositionMixin from './mouse' export default { mixins: [mousePositionMixin] } </script> 首先,每个mixin都可以定义自己的props、data,它们之间是无感的,所以很容易定义相同的变量,导致命名冲突对组件而言,如果模板中使用不在当前组件中的变量,那么就不会太容易知道这些变量在哪里定义的,这就是数据来源不清晰但是Vue.js 3.0设计的Composition API,就很好地帮助我们解决了mixins的这两个问题。
import { ref, onMounted, onUnmounted } from 'vue' export default function useMousePosition() { const x = ref(0) const y = ref(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 } }这里我们约定useMousePosition这个函数为hook 函数,然后在组件中使用:
<template> <div> Mouse position: x {{ x }} / y {{ y }} </div> </template> <script> import useMousePosition from './mouse' export default { setup() { const { x, y } = useMousePosition() return { x, y } } } </script> 除了在逻辑方面有优势,也会有更好的类型支持因为它们都是一些函数,在调用函数时,自然有的类型就被推导出来了,不像Options API所有的东西都是使用thisComposition API对tree-shaking友好,代码也更容易压缩Composition API属于API的增强,它并不是Vue.js 3.0 组件开发的范式,如果组件足够简单,还是可以使用Options API
RFC(Request For Comments),旨在为新功能进入框架提供一个一致且受控的路径
Vue.js 3.0中大规模启用RFC(Request For Comments),了解每一个feature采用或被废弃的原因
