本文将会全方位的介绍Vue3的新功能,新优化,新特性,以及升级指南。
由于Vue3正式版还未完全发布,最新的是Release Candidate版本,GitHub
Vue3会带来些什么?- 更快
- 更小
- 更易于维护
- 新功能和特性
Vue3重写了虚拟DOM的实现方法,初始渲染/更新可以提速达100%。
对于Vue2.x版本的虚拟DOM来说,Vue会遍历模板中的所有内容,并根据这些标签生成对应的虚拟DOM(虚拟DOM一般指采用key/value对象来保存标签元素的属性和内容),当有内容改变时,遍历虚拟DOM来diff找到对应的标签元素所对应的DOM节点,并改变其内容。例如下面这段内容:
number1
number2
number3
{{count}}
当触发双向绑定时,遍历所有的标签和
标签,找到{{count}}变量对应的
的DOM节点,并改变其内容。这对于那些纯静态
的节点进行diff其实是比较浪费资源的,当节点的数量很少时,表现并不明显,但是一旦节点的数量过大,在性能上就会慢很多。对此,Vue3再次基础上进行了优化主要有:
- 标记静态内容,并区分动态内容。
- 更新时只diff动态的部分。
针对上面的代码,Vue3中首先会区分出{{count}}这部分动态的节点,在进行diff时,只针对这些节点进行,从而减少资源浪费,提升性能,具体这部分逻辑可以参考源码。
事件缓存我们知道在vue2中,针对节点绑定的事件,每次触发都要重新生成全新的function去更新。在Vue3中,提供了事件缓存对象cacheHandlers,当cacheHandlers开启的时候,编译会自动生成一个内联函数,将其变成一个静态节点,这样当事件再次触发时,就无需重新创建函数直接调用缓存的事件回调方法即可。
开启cacheHandlers:
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = { id: "app" }
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", _hoisted_1, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = $event => (_ctx./confirm/iHandler($event)))
}, "确认"),
_createVNode("span", null, _toDisplayString(_ctx.vue3), 1 )
]))
}
关闭cacheHandlers:
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = { id: "app" }
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", _hoisted_1, [
_createVNode("button", { onClick: _ctx.confirmHandler }, "确认", 8 , ["onClick"]),
_createVNode("span", null, _toDisplayString(_ctx.vue3), 1 )
]))
}
基于Proxy的响应式对象
Proxy API对应的Proxy对象是ES2015就已引入的一个原生对象,用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。
从字面意思来理解,Proxy对象是目标对象的一个代理器,任何对目标对象的操作(实例化,添加/删除/修改属性等等),都必须通过该代理器。因此我们可以把来自外界的所有操作进行拦截和过滤或者修改等操作。例如下面的示例:
let foo = {
a:1,
b:2
}
let handler = {
set:(obj,key,value)=>{
console.log('set')
obj[key] = value
return true
}
}
let p = new Proxy(foo,handler)
p.a = 3 // 打印出console.log('set')
在Vue2.x中,使用Object.defineProperty()来实现响应式对象,对于一些复杂的对象,还需要循环递归的给每个属性增加上getter/setter监听器,这使得组件的初始化非常耗时,而Vue3中,composition-api提供了一种创建响应式对象的方法reactive,其内部就是利用了Proxy API来实现的,这样就可以不用针对每个属性来一一进行添加,减少开销提升性能。
更小 Tree shaking支持Tree shaking是一个术语,通常用于描述移除Javascript上下文中的未引用代码(dead-code),就像一棵大树,将那些无用的叶子都摇掉。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import 和 export。这个术语和概念在打包工具rollup和wepack中普及开来。
import {get} from './api.js'
let doSome = ()=>{
get()
}
doSome()
api.js代码:
let post = ()=>{
console.log('post')
}
export post
let get = ()=>{
console.log('get')
}
export get
上面代码中,api.js代码中的post方法相关内容是没有被引入和使用的,有了Tree shaking之后,这部分内容是不会被打包的,这就在一定程度上减少了资源的大小。使用Tree shaking的原理是ES6的模块静态分析引入,这就可以在编译时正确判断到底加载了什么代码,但是要注意import 和 export是ES6原生的而不是通过babel或者webpack转化的。
在Vue3中,对代码结构进行了优化,让其更加符合Tree shaking的结构,这样使用相关的api时,不会把所有的都打包进来,只会打包你用到的api,例如:
import Vue from 'vue'
new Vue()
Vue.nextTick(() => {})
const obj = Vue.observable({})
import { nextTick, observable,createApp } from 'vue'
nextTick(() => {})
const obj = observable({})
createApp({})
同时,例如
在Vue3的源码结构层面,从Flow改成了Typescript来编写,一般来说对于Javascript源码框架来说引入类型检测是非常重要的,不仅可以减少bug的产生,还可以规范一些接口的定义,Flowfacebook
出品,是一个静态类型检测器,有了它就可以在Javascript运行前找出常见的 bug,包括:
- 自动类型转换
- null 引用
- 可怕的 undefined is not a function
例如:
// @flow
function foo(x: number): number {
return x + 10
}
foo('hi') // 参数x需要为number类型,否则就会报错
message: '[flow] string (This type is incompatible with number See also: function call)'
这些特性和typescript非常吻合,所以在Vue3中直接采用了typescript来进行重写,从源码层面来提升项目的可维护性。
代码目录结构遵循monorepomonorepo是一种管理代码的方式,它的核心观点是所有的项目在一个代码仓库中,但是代码分割到一个个小的模块中,而不是都放在src这个目录下面。这样的分割,每个开发者大部分时只是工作在少数的几个文件夹以内的,并且也只会编译自己负责的模块,而且不会导致一个 IDE 打不开太大的项目之类的事情,这样很多事情就简单了很多。如下图:
目前诸如 Babel, React, Angular, Ember, Meteor, Jest 等等都采用了 Monorepo 这种方式来进行源码的管理。
Vue2.x的目录结构:
Vue3的目录结构:
在Vue2.x中,组件的主要逻辑是通过一些配置项来编写,包括一些内置的生命周期方法或者组件方法,例如下面的代码:
export default {
name: 'test',
components: {},
props: {},
data () {
return {}
},
created(){},
mounted () {},
watch:{},
methods: {}
}
这中基于配置的组件写法成为Options API(配置式API),Vue3的一大核心特性是引入了Composition API(组合式API),这使得组件的大部分内容都可以通过setup()方法进行配置,同时Composition API在Vue2.x也可以使用,需要通过安装@vue/composition-api来使用,代码如下:
npm install @vue/composition-api
...
import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);
下面就列举一些使用Composition API的例子:
ref或者reactive代替data中的变量:在Vue2.x中通过组件data的方法来定义一些当前组件的数据:
...
data() {
return {
name: 'test',
list: [],
}
},
...
在Vue3中通过ref或者reactive创建响应式对象:
import {ref,reactive} from 'vue'
...
setup(){
const name = ref('test')
const state = reactive({
list: []
})
return {
name,
state
}
}
...
ref将给定的值创建一个响应式的数据对象并赋值初始值(int或者string),reactive可以直接定义复杂响应式对象。
methods中定义的方法也可以写在setup()中:在Vue2.x中methods来定义一些当前组件内部方法:
...
methods: {
fetchData() {
},
}
...
在Vue3中直接在setup方法中定义并return:
...
setup(){
const fetchData = ()=>{
console.log('fetchData')
}
return {
fetchData
}
}
...
setup()中使用props和this:
在Vue2.x中,组件的方法中可以通过this获取到当前组件的实例,并执行data变量的修改,方法的调用,组件的通信等等,但是在Vue3中,setup()在beforeCreate和created时机就已调用,无法使用和Vue2.x一样的this,但是可以通过接收setup(props,ctx)的方法,获取到当前组件的实例和props:
export default {
props: {
name: String,
},
setup(props,ctx) {
console.log(props.name)
ctx.emit('event')
},
}
注意ctx和2.x中this并不完全一样,而是选择性地暴露了一些property,主要有[attrs,emit,slots]。
watch来监听对象改变Vue2.x中,可以采用watch来监听一个对象属性是否有改动:
...
data(){
return {
name: 'a'
}
},
watch: {
name(val) {
console.log(val)
}
}
...
Vue3中,在setup()中,可以使用watch来监听:
...
import {watch} from 'vue'
setup(){
let state = reactive({
name: 'a'
})
watch(
() => state.name,
(val, oldVal) => {
console.log(val)
}
)
state.name = 'b'
return {
state
}
}
...
在Vue3中,如果watch的是一个数组array对象,那么如果调用array.push()方法添加一条数据,并不会触发watch方法,必须重新给array赋值:
let state = reactive({
list: []
})
watch(
() => state.list,
(val, oldVal) => {
console.log(val)
}
)
state.list.push(1) // 不会触发watch
state.list = [1] // 会触发watch
此问题不知是否是Vue3.x特意加上的,有待正式版出来后在验证。
computed计算属性:Vue2.x中:
...
computed: {
storeData () {
return this.$store.state.storeData
},
},
...
Vue3中:
...
import {computed} from 'vue'
setup(){
const storeData = computed(() => store.state.storeData)
return {
storeData
}
}
...
当然,对于完整的Vue Composition API,各位同学可以参考文档
Fragments在Vue2.x中,中的内容必须由一个最外层的父元素包裹,代码如下:
...
...
结果发现React社区也遇到了同样的问题。他们想出的解决方案是一个名为Fragment的虚拟元素,使用方法如下:
class Hello extends React.Component {
render() {
return (
...
...
);
}
}
而在Vue3中,使用更加简单,可以直接省略最外层的元素,写法如下:
...
...
在Vue3中,



