2020年09月18日,vue3.0正式发布。作为一个大的版本更新,Vue3.0相比于 Vue2.0,自然有着不小的变化。
其中,Vue3的一大核心特性是引入了Composition API(组合式API),那么相比于vue2.0,Composition API有什么不一样?
一、Composition API
组合式 API和配置式 API有什么区别?
vue2.0(配置式 API)
在Vue2.x中,组件的主要逻辑是通过一些配置项来编写,包括一些内置的生命周期方法或者组件方法
export default {
name: 'test',
components: {},
props: {},
data () {
return {}
},
created(){},
mounted () {},
watch:{},
methods: {}
}
复制代码
我们通常将components、data、以及各种钩子和watch分开来写,然后通过options的形式传给vue实例。
vue 3.0(组合式 API)
setup() 函数是 vue3 中,专门为组件提供的新属性。它为我们使用 vue3 的 Composition API 新特性提供了统一的入口,通俗点来说就是啥玩意都能写到setup()里面。
import { fetchGetList } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
setup (props) {
const repositories = ref([])
const getList = async () => {
repositories.value = await fetchGetList()
}
onMounted(getUserRepositories)
watch(getUserRepositories)
return {
repositories,
getUserRepositories
}
}
复制代码
为什么要选择组合式api?
我们在进行组件式编程,同一个组件内可能要写非常多的功能,比如弹框、getList、add、update之类,在vue2.0时代,我们的组件代码可能会这么写:
vue2.0
export default {
name: '',
components: {
},
props: {
},
data() {
return {
// 踢足球
football: '',
// 打篮球
basketball: '',
// 打台球
....
}
},
created() {
// 踢足球
// 打篮球
},
methods: {
// 踢足球
playFootball() {
console.log(this.football)
},
// 打篮球
playBasketball() {
console.log(this.basketball)
}
// 打台球
....
}
}
</script>
复制代码
分散在各个地方的逻辑片段,使我们的组件越来越难以阅读,经常为了看一个功能要疯狂上下滑动屏幕,在各个区块里翻找和修改。
mixins
在vue2.0中还可以用mixins来进行抽象,但是用的时间长了,mixins就没那么香了
Mixins的优点:
可以将代码按照功能组织区分
Mixins的缺点:
存在冲突隐患,你有可能在使用过程中出现属性或函数的重名冲突
依赖关系不清晰,特别是在多个Mixins存在交流的情况下。
逻辑复用不够灵活,如果你需要在不同的组件间差异化或配置化使用Mixins的话。
vue3.0
那么vue3.0的组合式 API应该怎么写呢:
<script>
export default {
setup(props, ctx) {
// 踢足球
const useFootball = function(){
}
// 打篮球
const useBasketball = function(){
}
return {
useFootball,
useBasketball
}
}
};
</script>
复制代码
没有了各种分散的区块,我们可以写更少的代码,也更容易将可以复用的逻辑从组件里抽出到函数里。
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可以直接定义复杂响应式对象。为什么reactive可以定义复杂的响应式对象这个后面会有说明。
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')
},
}
复制代码
二、vue3.0的优化(更快、更小)
PatchFlag(静态标记)、hoistStatic(静态提升)
vue2.0的源码在处理虚拟DOM时,选择的是将template模板内的所有内容遍历,生成虚拟DOM,当有内容发生变化的时候,通过diff算法进行更新。但是HTML中除了双向绑定的动态数据,还有非常多的静态内容,每次都要参与遍历就会非常浪费性能。
可以看到vue3.0在生成虚拟DOM的时候,第二个 div 标签为绑定的变量,所以打上了 1 标签,代表的是 TEXT(文字),从而会将内容分为静态资源和动态内容,从而更新的时候只diff动态的部分。
具体的请看源码
我们平时在开发过程中写函数的时候,定义一些写死的变量时,都会将变量提升出去定义,Vue 3.0 在这方面也做了同样的优化
这代表了这个变量只会被创建一次,而后只需要引用就好了,从而提升性能
cacheHandler(事件缓存)
默认情况下 @click 事件被认为是动态变量,所以每次更新视图的时候都会追踪它的变化。
但是正常情况下,我们的 @click 事件在视图渲染前和渲染后,都是同一个事件,基本上不需要去追踪它的变化,所以 Vue 3.0 对此作出了相应的优化叫事件监听缓存。
由此可见, 设置了cacheHandler后,静态标记为8的的动态节点变成了静态节点,从而不参与diff,提升了性能。
响应式对象(Proxy对象与reactive)
从字面意思来理解,Proxy对象是目标对象的一个代理器,任何对目标对象的操作(实例化,添加/删除/修改属性等等),都必须通过该代理器。因此我们可以把来自外界的所有操作进行拦截和过滤或者修改等操作。
引用大佬的例子来展示下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')
复制代码
相信所有的前端面试的时候都被问过这个问题,vue的双向数据绑定是怎么实现的?
标准答案应该都差不多:
Vue实现数据双向绑定主要利用的就是: 数据劫持和发布订阅模式。所谓数据劫持,就是利用JavaScript的访问器属性,即Object.defineProperty()方法,当对对象的属性进行赋值时,Object.defineProperty就可以通过set方法劫持到数据的变化,然后通知发布者(主题对象)去通知所有观察者,观察者收到通知后,就会对视图进行更新。
在Vue2.x中,使用Object.defineProperty()来实现响应式对象,对于一些复杂的对象,还需要循环递归的给每个属性增加上getter/setter监听器,这使得组件的初始化非常耗时。vue3中,composition-api提供了一种创建响应式对象的方法reactive,其内部就是利用了Proxy API来实现的,这样就可以不用针对每个属性来一一进行添加,减少开销提升性能。
reactive的源码
vue3.0更新的核心功能除了上面的内容,还包括Tree shaking支持、以及对typescript的支持,以及Fragments、等等小的东西,当然还有最重要的vite,都是值得了解一下的。