简版
-
性能更高了,主要得益于响应式的原理换成了 proxy,VNode diff 的算法进行了优化。
-
体积更小了,删除了一些没必要或不常用到的 API,例如 filter、EventBus 等;按需导入,能配合 Webpack 支持 Tree Shaking。
-
对 TS 支持更好啦,因为它本身源码就是用 TS 重写的。
-
Composition API(组合 API),相比较 Vue2 的 options api,对于开发大型项目更利于代码的复用和维护。
-
新特性,例如 Fragment、Teleport、Suspense 等。
一、Vue3 介绍
关于vue3
的重构背景,尤大是这样说的:
Vue 新版本的理念成型于 2018 年末,当时 Vue 2 的代码库已经有两岁半了。比起通用软件的生命周期来这好像也没那么久,但在这段时期,前端世界已经今昔非比了
在我们更新(和重写)Vue 的主要版本时,主要考虑两点因素:首先是新的 JavaScript 语言特性在主流浏览器中的受支持水平;其次是当前代码库中随时间推移而逐渐暴露出来的一些设计和架构问题」
简要就是:
- 利用新的语言特性(es6)
- 解决架构问题
vue3 带来的哪些变化
对于 vue 的高阶开发者
- 新的 RFC 机制让 Vue 新语法的讨论更加高效和透明
- Vue3 提供了自定义渲染器让开发跨端应用时更加得心应手。
- 全部的模块使用 TypeScript 重构,能够带来更好的可维护性。
- 响应式系统可以单独抽离使用。
对于 vue 的普通用户
- 工程化工具 Vite 带来了更丝滑的调试体验。
- Vue3 内部的优化(响应式系统基于 Proxy,静态标记,tree-shaking 等),使得性能更高,体积更小
- Composition API 组合语法带来了更好的组织代码的形式。
- 内置了新的 Fragment、Teleport 和 Suspense 等组件。
非兼容变更
Global API
- 全局
Vue API
已更改为使用应用程序实例 - 全局和内部
API
已经被重构为可tree-shakable
模板指令
- 组件上
v-model
用法已更改 <template v-for>
和 非v-for
节点上key
用法已更改- 在同一元素上使用的
v-if
和v-for
优先级已更改 v-bind="object"
现在排序敏感v-for
中的ref
不再注册ref
数组
组件
- 只能使用普通函数创建功能组件
functional
属性在单文件组件(SFC)
- 异步组件现在需要
defineAsyncComponent
方法来创建
渲染函数
- 渲染函数
API
改变 $scopedSlots
property 已删除,所有插槽都通过$slots
作为函数暴露- 自定义指令 API 已更改为与组件生命周期一致
- 一些转换
class
被重命名了:v-enter
->v-enter-from
v-leave
->v-leave-from
- 组件
watch
选项和实例方法$watch
不再支持点分隔字符串路径,请改用计算函数作为参数 - 在
Vue 2.x
中,应用根容器的outerHTML
将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。VUE3.x
现在使用应用程序容器的innerHTML
。
其他小改变
destroyed
生命周期选项被重命名为unmounted
beforeDestroy
生命周期选项被重命名为beforeUnmount
[prop default
工厂函数不再有权访问this
是上下文- 自定义指令 API 已更改为与组件生命周期一致
data
应始终声明为函数- 来自
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
组件的生命周期。
Vue3.0 的设计目标是什么?做了哪些优化
一、设计目标
不以解决实际业务痛点的更新都是耍流氓,下面我们来列举一下Vue3
之前我们或许会面临的问题
-
随着功能的增长,复杂组件的代码变得越来越难以维护
-
缺少一种比较「干净」的在多个组件之间提取和复用逻辑的机制
-
类型推断不够友好
-
bundle
的时间太久了
而 Vue3
经过长达两三年时间的筹备,做了哪些事情?
我们从结果反推
- 更小
- 更快
- TypeScript 支持
- API 设计一致性
- 提高自身可维护性
- 开放更多底层功能
一句话概述,就是更小更快更友好了
更小
Vue3
移除一些不常用的 API
引入tree-shaking
,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了
更快
主要体现在编译方面:
- diff 算法优化
- 静态提升
- 事件监听缓存
- SSR 优化
下篇文章我们会进一步介绍
更友好
vue3
在兼顾vue2
的options API
的同时还推出了composition API
,大大增加了代码的逻辑组织和代码复用能力
这里代码简单演示下:
存在一个获取鼠标位置的函数
import { toRefs, reactive } from 'vue'
function useMouse() {
const state = reactive({ x: 0, y: 0 })
const update = (e) => {
state.x = e.pageX
state.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return toRefs(state)
}
我们只需要调用这个函数,即可获取x
、y
的坐标,完全不用关注实现过程
试想一下,如果很多类似的第三方库,我们只需要调用即可,不必关注实现过程,开发效率大大提高
同时,VUE3
是基于typescipt
编写的,可以享受到自动的类型定义提示
三、优化方案
vue3
从很多层面都做了优化,可以分成三个方面:
- 源码
- 性能
- 语法 API
源码
源码可以从两个层面展开:
- 源码管理
- TypeScript
源码管理
vue3
整个源码是通过 monorepo
的方式维护的,根据功能将不同的模块拆分到packages
目录下面不同的子目录中
这样使得模块拆分更细化,职责划分更明确,模块之间的依赖关系也更加明确,开发人员也更容易阅读、理解和更改所有模块源码,提高代码的可维护性
另外一些 package
(比如 reactivity
响应式库)是可以独立于 Vue
使用的,这样用户如果只想使用 Vue3
的响应式能力,可以单独依赖这个响应式库而不用去依赖整个 Vue
TypeScript
Vue3
是基于typeScript
编写的,提供了更好的类型检查,能支持复杂的类型推导
性能
vue3
是从什么哪些方面对性能进行进一步优化呢?
- 体积优化
- 编译优化
- 数据劫持优化
这里讲述数据劫持:
在vue2
中,数据劫持是通过Object.defineProperty
,这个 API 有一些缺陷,并不能检测对象属性的添加和删除
Object.defineProperty(data, 'a', {
get() {
// track
},
set() {
// trigger
},
})
尽管Vue
为了解决这个问题提供了 set
和delete
实例方法,但是对于用户来说,还是增加了一定的心智负担
同时在面对嵌套层级比较深的情况下,就存在性能问题
default {
data: {
a: {
b: {
c: {
d: 1
}
}
}
}
}
相比之下,vue3
是通过proxy
监听整个对象,那么对于删除还是监听当然也能监听到
同时Proxy
并不能监听到内部深层次的对象变化,而 Vue3
的处理方式是在getter
中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归
语法 API
这里当然说的就是composition API
,其两大显著的优化:
- 优化逻辑组织
- 优化逻辑复用
逻辑组织
一张图,我们可以很直观地感受到 Composition API
在逻辑组织方面的优势
相同功能的代码编写在一块,而不像options API
那样,各个功能的代码混成一块
逻辑复用
在vue2
中,我们是通过mixin
实现功能混合,如果多个mixin
混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰
而通过composition
这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可
同样是上文的获取鼠标位置的例子
import { toRefs, reactive, onUnmounted, onMounted } from 'vue'
function useMouse() {
const state = reactive({ x: 0, y: 0 })
const update = (e) => {
state.x = e.pageX
state.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return toRefs(state)
}
组件使用
import useMousePosition from './mouse'
export default {
setup() {
const { x, y } = useMousePosition()
return { x, y }
},
}
可以看到,整个数据来源清晰了,即使去编写更多的hook
函数,也不会出现命名冲突的问题
- https://juejin.cn/post/6850418112878575629#heading-5
- https://vue3js.cn/docs/zh
参考文献
-
https://juejin.cn/post/6850418112878575629#heading-5
-
https://vue3js.cn/docs/zh
-
https://vue3js.cn/docs/zh/guide/migration/introduction.html#%E6%A8%A1%E6%9D%BF%E6%8C%87%E4%BB%A4
-
https://composition-api.vuejs.org/zh/#api-%E4%BB%8B%E7%BB%8D