语法API优化
Vue3的合成型API(Composition API)
Vue2使用选项类型API(Options API)
Vue3使用合成型API(Composition API)
Vue核心团队将Composition API描述为“一组基于功能的附加API,可以灵活地组合组件逻辑”。
使用
1 | <!-- vue2 --> |
1 | <!-- vue3 --> |
上面的setup它只是一个函数,向模板返回属性和函数。就是这样。我们在这里声明所有的反应式属性、计算属性、watchers和生命周期钩子,然后返回它们,这样它们就可以在模板中使用。没有从setup函数中返回的东西将不能在模板中使用
作用及意义
1.优化逻辑组织
Options API 的设计是按照 methods、computed、data、props 这些不同的选项分类,当组件小的时候,这种分类方式一目了然;但是在大型组件中,一个组件可能有多个逻辑关注点,当使用 Options API 的时候,每一个关注点都有自己的 Options,如果需要修改一个逻辑点关注点,就需要在单个文件中不断上下切换和寻找。
Composition API,它有一个很好的机制去解决这样的问题,就是将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去。
2.优化逻辑复用
当我们开发项目变得复杂的时候,免不了需要抽象出一些复用的逻辑。在 Vue.js 2.x 中,我们通常会用 mixins 去复用逻辑
mixin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 定义一个混入对象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"使用mixin最大的缺点是我们不知道到底在组件内加了什么,导致命名冲突和数据来源不清晰。。
solt插槽
solt虽然可以插入代码但是只能在标签内使用,局限性较大。
使用合成API
1 | // a.js |
组件的更新
teleport 组件
teleport 组件它只是单纯的把定义在其内部的内容转移到目标元素中,在元素结构上不会产生多余的元素,当然也不会影响到组件树,它相当于透明的存在。
1 | <teleport to="body"> |
把 dialog 组件渲染的这部分 DOM 挂载到 body 下面,不会受到父级样式的影响。
为什么要有这个组件?
为了有更好的代码组织体验。比如:有时,组件模板的一部分在逻辑上属于此组件,但从技术角度来看(如:样式化需求),最好将模板的这一部分移动到 DOM 中的其他位置。例如我们常用的弹窗、悬浮框、全局提示。
Suspense 异步组件
它会暂停你的组件渲染,在异步组件加载完成并完全渲染之前 suspense 会先显示 #fallback 插槽的内容 。
1 | <Suspense> |
模板指令更新
- 组件上 v-model 用法已更改
在 2.x 里,使用 v-model 等同于向组件传递一个 value 属性,同时监听一个 input 事件:Vue2只支持绑定一个值,绑定多个值需要使用v-bind.sync1
2
3
4
5<ChildComponent v-model="pageTitle" />
<!-- 是以下代码的简写: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" /><ChildComponent :foo.sync="bar"></ChildComponent><ChildComponent :foo="bar" @update:foo="val => bar = val"></ChildComponent>
在3.x中要修改一个model的名称的话,现在我们可以给 v-model 传递一个 参数 以取代此前的 model 选项:1
2
3
4
5
6
7
8<ChildComponent v-model="pageTitle" />
// 是以下代码的简写
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>还支持自定义修饰符1
2
3<ChildComponent v-model:title="pageTitle" />
//是以下代码的简写:
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" /> <template v-for>和非 -v-for节点上key用法已更改- 在同一元素上使用的 v-if 和 v-for 优先级已更改。(3.x 版本中 v-if 总是优先于 v-for 生效。)
- v-bind=”object” 现在排序敏感
1
2
3
4<!-- template2.x -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>1
2
3
4
5
6
7
8
9<!-- template3.x -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>
<!-- template3.x -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div> - v-for 中的 ref 不再注册 ref 数组
其他改变
- destroyed 生命周期选项被重命名为 unmounted
- beforeDestroy 生命周期选项被重命名为 beforeUnmount
- prop default 工厂函数不再有权访问 this 是上下文
- 自定义指令 API 已更改为与组件生命周期一致
- data 应始终声明为函数(Vue2是可以写为object的)
- 来自 mixin 的 data 选项现在可简单地合并
- attribute 强制策略已更改
- 一些过渡 class 被重命名
- 组建 watch 选项和实例方法 $watch 不再支持以点分隔的字符串路径。请改用计算属性函数作为参数。
<template>没有特殊指令的标记 (v-if/else-if/else、v-for 或 v-slot) 现在被视为普通元素,并将生成原生的<template>元素,而不是渲染其内部内容。- 在 Vue 2.x 中,应用根容器的 outerHTML 将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。Vue 3.x 现在使用应用容器的 innerHTML,这意味着容器本身不再被视为模板的一部分。
移除的 API
- keyCode 支持作为 v-on 的修饰符
- $on,$off 和 $once 实例方法
- 过滤(filter)
- 内联模板 attribute
- $destroy 实例方法。用户不应再手动管理单个 Vue 组件的生命周期。
源码优化
源码管理方式
源码的优化主要体现在使用 monorepo 和 TypeScript 管理和开发源码,这样做的目标是提升自身代码可维护性。
Vue.js 2.x 的源码托管在 src 目录,然后依据功能拆分出了 compiler(模板编译的相关代码)、core(与平台无关的通用运行时代码)、platforms(平台专有代码)、server(服务端渲染的相关代码)、sfc(.vue 单文件解析相关代码)、shared(共享工具代码) 等目录:
而到了 Vue.js 3.0 ,整个源码是通过 monorepo 的方式维护的,根据功能将不同的模块拆分到 packages 目录下面不同的子目录中:
monorepo 把这些模块拆分到不同的 package 中,每个 package 有各自的 API、类型定义和测试。这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性。
另外一些 package(比如 reactivity 响应式库)是可以独立于 Vue.js 使用的,这样用户如果只想使用 Vue.js 3.0 的响应式能力,可以单独依赖这个响应式库而不用去依赖整个 Vue.js,减小了引用包的体积大小,而 Vue.js 2 .x 是做不到这一点的。
TypeScript
重构 Vue2.0 的时候,使用了 Flow 工具,但是在 Vue.js 3.0 的时候抛弃 Flow 转而采用 TypeScript 重构了整个项目
原因主要是TypeScript提供了更好的类型检查,能支持复杂的类型推导;
性能优化
源码体积优化
移除一些冷门的 feature(比如 filter、inline-template 等);
在 Vue 3 中,全局和内部 API 都经过了重构, 加强对 tree-shaking 技术的支持,减少打包体积。
如果在项目中没有引入 Transition、KeepAlive 等组件,那么它们对应的代码就在webpack打包时会被标记,然后压缩阶段会利用例如 uglify-js、terser 等压缩工具真正地删除这些没有用到的代码。这样也就间接达到了减少项目引入的 Vue.js 包体积的目的。
数据劫持优化
Vue.js 1.x 和 Vue.js 2.x 内部都是通过 Object.defineProperty 这个 API 去劫持数据的 getter 和 setter,具体是这样的:
1 | Object.defineProperty(data, 'a',{ |
它必须预先知道要拦截的 key 是什么,所以它并不能检测对象属性的添加和删除。尽管 Vue.js 为了解决这个问题提供了 $set 和 $delete 实例方法,但是对于用户来说,还是增加了一定的心智负担。
其次就是如果对象嵌套层级过多的话,如果要劫持内部属性的话就需要递归整个对象,对性能也有一部分影响。
reactive –> Proxy
Vue.js 3.0 使用了 Proxy API 做数据劫持,主要通过 reactive 创建响应式数据:
1 | function reactive (target) { |
get
1 | const get = /*#__PURE__*/ createGetter() |
set
1 | const set = /*#__PURE__*/ createSetter() |
关于proxy
- 由于它劫持的是整个对象,那么对于对象的属性的增加和删除都能检测到。
- Proxy API 并不能监听到内部深层次的对象变化,因此 Vue.js 3.0 的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归,这样无疑也在很大程度上提升了性能
diff算法优化
静态节点标记
它通过编译阶段对静态模板的分析,编译生成了 Block tree。Block tree 是一个将模版基于动态节点指令切割的嵌套区块,每个区块内部的节点结构是固定的,而且每个区块只需要以一个 Array 来追踪自身包含的动态节点。借助 Block tree,Vue.js 将 vnode 更新性能由与模版整体大小相关提升为与动态内容的数量相关,这是一个非常大的性能突破
Slot的编译优化
Vue2.x中,如果有一个组件传入了slot,那么每次父组件更新的时候,必定会强制使子组件update,造成性能的浪费。这是由于2.x中,组件的插槽会被当成组件的一个普通children,因此在2.x里面的处理就是只要一个component中传入了slot,那么如果父组件更新,必定会update子组件。
Vue3优化了Slot的生成,父子组件可以单独更新。将所有的slot统一编译一个函数,当我们要将一个slot传给子组件的时候,将这个函数传给子组件,而调用这个函数,是子组件的行为,所以依赖的变化,就成为了子组件的而不是父组件的,所以当依赖变动时,我们只需要重新渲染子组件,而不必再去渲染父组件了
事件侦听函数的缓存优化
比如对onclick进行一个缓存处理,当第一次渲染的时候,因为不存在_cache[1],所以vue会自动生成一个内联函数,给cache[1]赋值_cache[1] = () => {},使我们能自动去调用组件上最新的onclick。在后续的更新中,我们只需要从缓存_cache[1]中去读取同一个函数即可,而既然是同一个函数,那也没有被更新的必要了,所以@click也会被看成静态的。
这种优化在要给组件传入一个函数时尤为明显,如果不使用事件监听缓存的话,那么在父组件更新的时候,子组件就会跟着更新。而通过事件监听缓存机制,我们传给子组件的函数,它会在调用时动态地去找到组件中最新的那个函数,不需要对子组件进行更新。
ssr渲染优化
在SSR渲染时,会在服务端尽可能地将静态部分处理为字符串返回。如果存在动态节点,也会竟可能放在字符串里面,动态数据编译成模板字符串推入。