前不久Vue3的RC版本终于发布,一直没时间研究,这篇文章我将以下面的结构,为大家整体梳理下Vue3,顺便对比下vue2.x和react hook结合着带大家看看,可能有些长,耐心看完相信会有收获(面试热点)。
提示:首先我们先了解下,是什么让尤雨溪决定重写vue,这将也是Vue2.x与Vue3的差异性对比,vue的一次重大升级。(面试热点)
后续敬请期待,Vue2.x源码解析,努力码字中。。。
如今的前端Vue - React - Angular三足鼎立,彼此之间相互牵制,可谓明争暗斗多年,自古以来落后就要挨打,如今Vue就是意识到了自己的不足,在寻找突破和提升,那么究竟那些不足呢?相信大家使用时候也多多少少感觉到了,后面将细细说明。
当前github 上三大框架的star情况, vue-175K , react -159k , angular- 67.3k
1.主流浏览器对新的JavaScript语言特性的普遍支持。
2.当前Vue代码库随着时间的推移而暴露出来的设计和体系架构问题。
提示:具体那里不足会在下面vue2和vue3对比时候说明
Vue2.x | Vue3 |
---|---|
beforeCreate | 使用 setup() |
created | 使用 setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
整体来看其实变化不大,使用setup代替了之前的beforeCreate和created,其他生命周期名字有些变化,功能都是没有变化的
熟悉vue的朋友都知道,vue2.x双向绑定的核心是Object.defineProperty(),那为什么要换掉呢,我们看看他们的语法就知道了。
重点:vue为什么对数组对象的深层监听无法实现,因为组件每次渲染都是将data里的数据通过defineProperty双向绑定上,之前没有后加的属性是不会被绑定上,也就不会触发更新渲染
Object.defineProperty( Obj, 'name', {
enumerable: true, //可枚举
configurable: true, //可配置
// writable:true, //跟可配置不能同时存在
// value:'name', //可写死直
get: function () {
return def
},
set: function ( val ) {
def = val
}
} )
//两个参数,对象,13个配置项
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : 37;
},
set:function(){
},
...13个配置项
};
const p = new Proxy({
}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
对比了上面两种语法是不是就懂了,defineProperty只能绑定首次渲染时候的属性,Proxy需要的是整体,不需要关心里面有什么属性,而且Proxy的配置项有13种,可以做更细致的事情,这是之前的defineProperty无法达到的
1.vue2.x之所以只能兼容到IE8就是因为defineProperty无法兼容IE8,其他浏览器也会存在轻微兼容问题
2.proxy的话除了IE,其他浏览器都兼容,这次vue3还是使用了它,说明vue3直接放弃了IE的兼容考虑,个人感觉已经没人用IE了
vue2.x提供类似于HTML的模板语法,但是,它是将模板编译成渲染函数来返回虚拟DOM树。Vue框架通过递归遍历两个虚拟DOM树,并比较每个节点上的每个属性,来确定实际DOM的哪些部分需要更新。
由于现代JavaScript引擎执行的高级优化,这种有点暴力的算法通常非常快速,但是DOM的更新仍然涉及许多不必要的CPU工作,那么如何解决呢?
引用尤雨溪:
为了实现这一点,编译器和运行时需要协同工作:编译器分析模板并生成带有优化提示的代码,而运行时尽可能获取提示并采用快速路径。这里有三个主要的优化:
首先,在DOM树级别。我们注意到,在没有动态改变节点结构的模板指令(例如v-if和v-for)的情况下,节点结构保持完全静态。如果我们将一个模板分成由这些结构指令分隔的嵌套“块”,则每个块中的节点结构将再次完全静态。当我们更新块中的节点时,我们不再需要递归遍历DOM树 - 该块内的动态绑定可以在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟DOM的大部分开销。
其次,编译器积极地检测模板中的静态节点、子树甚至数据对象,并在生成的代码中将它们提升到渲染函数之外。这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。
第三,在元素级别。编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。
综合起来,这些技术大大改进了我们的渲染更新基准,Vue 3有时占用的CPU时间不到Vue 2的十分之一。
vue2.x中使用的都是js,并没有类型系统这个概念,现如今typescript异常火爆,它的崛起是有原因的,因为对于规模很大的项目,没有类型声明,后期维护和代码的阅读都是头疼的事情,所以广大码农迫切的需要vue能完美支持ts。
最终vue3 借鉴了react hook实现了更自由的编程方式,提出了Composition API,Composition API不需要通过指定一长串选项来定义组件,而是允许用户像编写函数一样自由地表达、组合和重用有状态的组件逻辑,同时提供出色的TypeScript支持。
vue2官方说的运行时打包师23k,但这只是没安装依赖的时候,随着依赖包和框架特性的增多,有时候不必要的,未使用的代码文件都被打包了进去,所以后期项目大了,打包文件会特别多还很大。
引用尤雨溪:
注释:这些小改动就不做更细的说明,只列举下。
详细使用看vue2的迁移部分
项目 | npm | 仓库 |
---|---|---|
@vue/babel-plugin-jsx | Github | |
eslint-plugin-vue | Github | |
@vue/test-utils | Github | |
vue-class-component | Github | |
vue-loader | Github | |
rollup-plugin-vue | Github |
看了上面Vue2.x和Vue3的对比,是不是感觉好牛逼,有点迫不及待的想体验了呢?
我们这里使用vite脚手架安装
//脚手架vite
npm init vite-app hello-vue3 | yarn create vite-app hello-vue3
//脚手架vue-cli
npm install -g @vue/cli | yarn global add @vue/cli
vue create hello-vue3
# select vue 3 preset
这里以改变helloWorld.vue组件为例,代码重点部分给了注释
//dom 里的东西基本上都是没有变的
<template>
<h1>{
{
msg }}</h1>
<button @click="increment">
count: {
{
state.count }}, double: {
{
state.double }},three:{
{
three }},refnum:{
{
refnum}}
</button>
</template>
<script>
//这里就是Vue3的组合Api了,这里跟react的 import { useState ,useEffect } from 'react' 有些类似,需要用啥引啥
import {
ref, reactive, computed ,watchEffect,watch} from "vue";
export default {
name: "HelloWorld",
props: {
msg: String,
},
//上面对比的时候说过,setup相当于beforeCreate 和created,简单理解就是初始化
setup() {
//这里通过reactive使state成为相应状态(后面会详细介绍)
const state = reactive({
count: 0,
//计算属性computed的使用更灵活了
double: computed(() => state.count * 2),
});
//computed也可以单独拿出来使用
const three = computed(() => state.count * 3)
//ref跟reactive作用一样都是用来双向绑定的,ref的颗粒度更小(后面详细对比)
const refnum = ref()
//这里的watchEffect只要里面的变量发生了改变就会执行,并且第一次渲染会立即执行,没有变化前后返回参数,无法监听整个reactive
watchEffect(() => {
refnum.value = state.count;
console.log(state, "watchEffect");
});
//watch里第一个参数是监听需要的变量,第二个是执行的回调函数,
watch(refnum,(a,b)=>{
console.log(a,b,'watch,a,b')
})
//所有的方法里再也不需要用this了,这是很爽的
function increment() {
state.count++;
}
//组中模板中需要的变量,都要通过return给暴露出去,就像当初data({return { } }) 是一样的
return {
state,
increment,
three,
refnum
};
},
};
</script>
上面对比的时候也说了,生命周期命名改变了更有语义化了,使用方法也改变(这里有点像react 的useEffect(()=>{ })),使用前需要我们在组合Api里获取。
<script>
import {
reactive,
computed,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
onUnmounted,
onBeforeUnmount,
} from "vue";
export default {
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2),
});
function increment() {
state.count++;
}
onUpdated(() => {
console.log("onUpdated");
});
onUnmounted(() => {
console.log("onUnmounted");
});
onBeforeUnmount(() => {
console.log("onBeforeUnmount");
});
onBeforeUpdate(() => {
console.log("onBeforeUpdate1");
});
onMounted(() => {
console.log("onMounted");
});
onBeforeMount(() => {
console.log("onBeforeMount");
});
console.log("setup");
return {
state,
increment,
};
},
};
</script>
setup替代了以前的 beforeCreate 和 created ,类似于初始化的功能
父组件:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Baby张 Vue3 RC" />
//这里传参给子组件
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
import {
provide } from "vue";
export default {
name: "App",
components: {
HelloWorld,
}
};
</script>
子组件:
//props 接收的父组件传的参数,这就有点像react的props了
//ctx 这个参数表示的当前对象实例,也就个是变相的this
setup(props,ctx){
console.log(props.msg, ctx, "app-setup");
}
我们来看看打印的结果
如果你还想要更多当前组件相关的属性,还可以从组合Api 里引用 getCurrentInstance
import {
getCurrentInstance } from "vue";
const all = getCurrentInstance()
console.log(all, "app-setup");
看到这3个可能大家一脸懵逼,先看看大概用法,然后再告诉大家好记的方法
import {
ref , toRef , toRefs } from 'vue'
setup(){
const obj = {
age:12}
//初始化设置个响应式变量tor,函数中读取值要tor.value
let tor = ref(0)
//这里将对象转化成响应性,并设置key值,函数中读取值要toR._object
let toR = toRef(obj,'toR')
const state = reactive({
num:1,
name:'baby张'
})
return {
tor,
roR,
//toRefs针对的是使用了reactive的响应式对象,可以理解为将对象拆分成多个ref并进行双向绑定,外界可以读取到响应式的所有属性
...toRefs(state)
}
}
ref 就当作简单的双向绑定变量
toRef 就是把不是响应式的对象转化成响应式
toRefs 就是把响应式的reactive对象,分解成无数的 ref 双向绑定
上面的 demo 中多多少少都用到了,用法也很简单,就说下注意点:
使用方法:
//这里的watchEffect只要里面的变量发生了改变就会执行,并且第一次渲染会立即执行,没有变化前后返回参数,无法监听整个reactive
watchEffect(() => {
refnum.value = state.count;
console.log(state, "watchEffect");s
});
//watch里第一个参数是监听需要的变量,第二个是执行的回调函数,
watch(refnum,(a,b)=>{
console.log(a,b,'watch,a,b')
})
看名字就知道都是用来监听的,但有一些区别:(亲测)
import {
h } from 'vue'
const Fun = (props, context) => {
//这里h的用法跟以前还是一样的
return h(p, context.attrs, context.slots)
}
export default Fun
放上尤雨溪的模板写法:
import {
ref, computed, watch, onMounted } from 'vue'
const App = {
template: `
<div>
<span>count is {
{
count }}</span>
<span>plusOne is {
{
plusOne }}</span>
<button @click="increment">count++</button>
</div>
`,
setup() {
// reactive state
const count = ref(0)
// computed state
const plusOne = computed(() => count.value + 1)
// method
const increment = () => {
count.value++ }
// watch
watch(() => count.value * 2, val => {
console.log(`count * 2 is ${
val}`)
})
// lifecycle
onMounted(() => {
console.log(`mounted`)
})
// expose bindings on render context
return {
count,
plusOne,
increment
}
}
}
上面我主要在大的方向给大家做了介绍,其实还有好多细节的改动。
比如:一步组件、指令、solt、过度class…更多更改看官网Vue2迁移
TS的支持也是Vue3的重中之重,在vue2.x版本ts里应该好多人用过vue-property-decorator 来写vue里的TS,有些装饰器器和属性也比较难以理解,比如:
@Emit
@Inject
@Provice
@Prop
@Watch
@Model
Mixins
都需要一些学习成本,才能下手。但没办法只能硬着头皮用,外物终究使外物。写习惯了react + ts 突然用vue 总感觉少了什么,也没法像react 一样随心所欲的在js里声明类型,这也是Vue3重构的原因之一。
至于TS还不知怎么用的小伙伴,看看TS官网先学学
到这里大家对Vue3 的RC版本已经有了一定了解,还没行动的小伙伴,赶紧行动起来吧。
react 在v16.8 后的版本就已经全面支持hook了,笔者也是使用 hook 和 TS 完整的开发完了4个项目,收获满满。
对 React hook 还不了解的小伙伴,看我之前文章(React hook 10种 Hook (详细介绍及使用))
我只对新版的Vue3 和 React v16.8 做比较,只是我个人看法勿喷,望指正
对比方向 | Vue3 | React v16.8 | 总结 |
---|---|---|---|
学习成本 | 低 | 高 | setup 和 hook 比以前其实都简洁很多 |
代码可读性 | 高 | 高 | 写组件更像在写js代码 |
TS | 支持 | 支持 | 这也是vue3重构原因 |
心智负担 | 低 | 高 | hook 方法的注意点、优化点、渲染问题、都有人为把控 |
欢迎程度 | 高 star 175k | 高 star 159k | 都是很受欢迎、了不起的框架 |
打包体积 | 小 | 大 | Vue3 对打包进行了深度优化,体积原来一半,应该很棒 |
diff算法 | 更快 | 快 | Vue3 不需要递归对比,理论上应该更快 |
使用感受 | 爽 | 爽 | 都是一大突破,用起来很爽 |
虽然现在我自己用react 比较多,但还是很期待Vue3 的正式发布,两个都是很棒的框架,至少我还写不出来,期待他们的不断完善和成长。
引用尤大大的话:
setup 和 React Hooks 有一定的相似性,具有同等的基于函数抽取和复用逻辑的能力,但也有很本质的区别。React Hooks 在每次组件渲染时都会调用,通过隐式地将状态挂载在当前的内部组件节点上,在下一次渲染时根据调用顺序取出。而 Vue 的 setup() 每个组件实例只会在初始化时调用一次 ,状态通过引用储存在 setup() 的闭包内。这意味着基于 Vue 的函数 API 的代码:
全文14200多字,第一次写这么长文章,还望支持,批评指正!
后续敬请期待,Vue2.x源码解析,努力码字中。。。