vue3和vue2对比
- compostion api:组合api/注入api(3合成型api Composition API | 2选项型api Options API)
- 双向数据绑定、响应式原理api的改变
- this在vue3中与vue2中代表着完成不一样的东西
- fragment支持多根节点组件
- diff算法的优化
- hoistStatic静态提升
- cacheHandlers事件侦听器缓存
- teleport传送门、suspense悬挂
- v-if和v-for优先级
- 指令与插槽不同
- ssr渲染
- 更好的ts支持
- 自定义渲染api
- 按需编译,体积比vue2.x更小
- 生命周期
Composition API vs Options API
Options API
- 代码风格:data选项写数据,methods选项写函数...,一个功能逻辑的代码分散
- 优点:易于学习和使用,写代码的位置已经约定好
- 缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑复杂代码多了不好阅读
- 补充:虽然提供mixins用来封装逻辑,但是出现数据函数覆盖的概率很大,不好维护
// Options API <script> export default { data() { // 定义变量 return { name: '搞前端的半夏', }; }, methods: { // 定义方法 consoleUser() { console.log(`Hello ${this.name}`); }, }, mounted() { // 一些生命周期,如这里等挂载完成 this.consoleUser(); }, }; </script>
Composition API
- 代码风格:一个功能逻辑的代码组织在一起全部写在setup中(包含数据,函数...)
- 优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护
- 缺点:需要有良好的代码组织能力和拆分逻辑能力
// Composition API <script> import { onMounted, ref, toRefs, reactive } from 'vue'; export default { setup() { const name = ref(''); // 定义变量 // 定义方法 const doIt = () => console.log(`Hello ${name.value}`); // 一些生命周期 onMounted(() => { consoleUser(); }); return { name }; }, }; </script>
- setup方法
setup() 函数接收两个参数 props 和 context。
第一个参数 props,它是响应式的,当传入新的 prop 时,它将被更新。
第二个参数 context 是一个普通的 JavaScript 对象,它是一个上下文对象,暴露了其它可能在 setup 中有用的值。
注意:在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。
<script setup>语法糖 https://cn.vuejs.org/api/sfc-script-setup.html
Options API 和 Composition API 之间生命周期钩子的映射
Vue2 Options-based API | Vue Composition API |
---|---|
beforeCreate | setup() |
created | setup() |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
errorCaptured | onErrorCaptured |
vue2与vue3的响应式原理简单对比
vue2的响应式原理
- 对象类型
原理:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
通过Object.defineProperty()来拦截数据,将数据转换成getter/setter的形式(vue底层做了无限的递归,能够监视data中所有层次的数据),在访问数据时调用getter函数,在修改数据时调用setter函数。然后利用发布-订阅模式,在数据变动时触发依赖,也即发布更新给订阅者,订阅者收到消息后进行相应的处理(重新解析模板,生成虚拟DOM,新旧虚拟DOM对比,更新页面)。
- 数组类型
原理:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
通过vue封装过了的修改数组方法:push pop shift unshift splice sort reverse可以实现响应式
这些数组的方法被Vue所管理,里面的这些方法不再是正常Array原型对象上的了,它除了可以正常调用Array原型对象上的这些方法外,还能够重新解析模板等一系列操作
缺陷:
对于Object类型的数据,getter/setter只能追踪一个数据是否被修改,无法追踪数据是否新增或删除属性。因此,Vue2中提供了Vue.set()和Vue,delete()方法来添加或删除属性,确保新添加的属性也具有响应式。
对于数组类型的数据,一些不调用原型方法修改数据的方式,Vue2也监测不到。
对于深度监听需要一次性进行递归遍历,影响性能。
vue3的响应式原理
- 基本类型
原理:响应式依然是靠Object.defineProperty()
的get
与set
完成的。
- 对象类型
原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
优点:
Vue3基于ES6新增的Proxy对象实现数据代理以及通过Reflect对源数据进行操作,它解决了(1)Vue2中无法追踪数据新增或删除属性的问题。另外,Proxy(2)可以直接监听数组,无需像Vue2响应式那样需要重写数组方法进行拦截。
this在vue2与vue3的不同
- data()里this指向的不同情况
- 脚手架创建的vue2,写法也是vue2
vue2的data函数里的this是vue实例
<script> export default { data(){ return { d:this } }, mounted(){ console.log("vue2this",this.d) } } </script>
-
- 脚手架创建的vue3,但写法是vue2
this是Proxy
<script> export default { data(){ return { d:this } }, mounted(){ console.log("是vue3,但写法是vue2的this",this.d) } } </script>
-
- 脚手架创建的vue3,写法也是vue3
vue3的this是undefined
<script lang="ts" setup> import { onMounted } from 'vue' onMounted(() => { console.log('vue3的this', this) }) </script>
- 访问props问题
vue2访问props的属性
mounted() { console.log('title: ' + this.title) }
vue3访问props的属性
setup(props) { onMounted(() => { console.log('title: ' +props.title) }) }
- 触发事件问题
vue2调用this.$emit并传入有效负载对象
login (){ this.$emit('login', { username: this.username, password: this.password }) }
vue3调用emit来触发事件
setup(props, { emit }) { const login = () => { emit('login', { username: state.username, password: state.password }) } }
fragment支持多根节点组件
vue2不支持碎片
vue3支持,可以拥有多个根节点
<template> <div>啊啊啊</div> <div>哦哦哦</div> </template>
diff算法的优化
vue2中的虚拟dom是全量的对比(每个节点不论写死的还是动态的都会一层一层比较,这就浪费了大部分事件在对比静态节点上)
vue3新增了静态标记(patchflag)与上次虚拟节点对比时,只对比带有patch flag的节点(动态数据所在的节点);可通过flag信息得知当前节点要对比的具体内容。
例如:下面的模板包含一个div,div内包含三个段落,其中前两个段落是静态固定不变的,而第三个段落的内容绑定的msg属性,当msg改变的时候,Vue会生成新的虚拟DOM然后和旧的进行对比。
<div> <p>云驻共创</p> <p>如何评价 vue3</p> <p>{{msg}}</p> </div>
当视图更新时,只对动态节点部分进行diff运算,减少了资源的损耗。Patchflag是个枚举,取值为1代表这个元素的文本是动态绑定的,取值为2代表元素的class是动态绑定的。
hoistStatic静态提升
vue2无论元素是否参与更新,每次都会重新创建然后再渲染。
vue3对于不参与更新的元素,会做静态提升(提升到了渲染函数 render 之外),只会被创建一次,在渲染时直接复用即可。
cacheHandlers事件侦听器缓存
Vue3 的cacheHandler
可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数,加一个click
事件。
vue2.x中,绑定事件每次触发都要重新生成全新的function去更新,cacheHandlers 是Vue3中提供的事件缓存对象,当 cacheHandlers 开启,会自动生成一个内联函数,同时生成一个静态节点。当事件再次触发时,只需从缓存中调用即可,无需再次更新。
默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化,但是同一个函数没必要追踪变化,直接缓存起来复用即可。
teleport传送门、suspense悬挂
vue3提供了Teleport组件可将部分Dom移动到vue app之外的位置,比如Dialog组件
Vue3 提供Suspense
组件,允许程序在等待异步组件时渲染兜底的内容,如 loading ,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default
和fallback
。Suspense
确保加载完异步内容时显示默认插槽,并将fallback
插槽用作加载状态。 若想在 setup 中调用异步请求,需在 setup 前加async
关键字。这时,会受到警告async setup() is used without a suspense boundary
。
解决方案:在父页面调用当前组件外包裹一层Suspense
组件。
v-if和v-for优先级
vue2
我们最好不要把v-if和v-for同时用在一个元素上,这样会带来性能的浪费(每次都要先渲染才会进行条件判断)
v-for 优先于 v-if 生效 <div v-if="index == 1" v-for="(item,index) in arr" :key="index">{{item}}</div>
vue3
v-if 优先于 v-for 生效 <div v-if="index == 1" v-for="(item,index) in arr" :key="index">{{item}} </div>
vue中会给我们报警告:
意思就是:属性“index”在渲染期间被访问,但未在实例上定义(v-if先进行判断,但是这时候v-for还没有渲染,所以index是找不到的)
指令与插槽不同
vue2:vue2中使用slot可以直接使用slot;v-for与v-if在vue2中优先级高的是v-for指令,而且不建议一起使用。
vue3:vue3中必须使用v-slot的形式;vue3中v-for与v-if,只会把当前v-if当做v-for中的一个判断语句,不会相互冲突;vue3中移除keyCode作为v-on的修饰符,当然也不支持config.keyCodes;vue3中移除v-on.native修饰符;vue3中移除过滤器filter。
ssr渲染
Vue2 中也是有 SSR 渲染的,但是 Vue3 中的 SSR 渲染相对于 Vue2 来说,性能方面也有对应的提升。
当存在大量静态内容时,这些内容会被当作纯字符串推进一个 buffer 里面,即使存在动态的绑定,会通过模版插值潜入进去。这样会比通过虚拟 dmo 来渲染的快上很多。
当静态内容大到一个量级的时候,会用_createStaticVNode 方法在客户端去生成一个 static node,这些静态 node,会被直接 innerHtml,就不需要再创建对象,然后根据对象渲染。
更好的ts支持
vue2不适合使用ts,原因在于vue2的Option API风格。options是个简单对象,而ts是一种类型系统、面向对象的语法。两者有点不匹配。
在vue2结合ts的具体实践中,要用 vue-class-component 强化 vue 组件,让 Script 支持 TypeScript 装饰器,用 vue-property-decorator 来增加更多结合 Vue 特性的装饰器,最终搞的ts的组件写法和js的组件写法差别挺大。
在vue3中,量身打造了defineComponent函数,使组件在ts下,更好的利用参数类型推断 。Composition API 代码风格中,比较有代表性的api就是 ref 和 reactive,也很好的支持了类型声明。
自定义渲染api
Vue3 提供的createApp
默认是将 template 映射成 html。但若想生成canvas
时,就需要使用custom renderer api
自定义render生成函数。
//自定义runtime-render函数 import{createApp}from'./runtime-render' import App from './src/App' createApp(App).mount('#app')
vue2.x项目架构对于weex(移动端跨平台方案)和myvue(小程序上使用)等渲染到不同平台不太友好,vue3.0推出了自定义渲染API解决了该问题。下面我们先看vue2和vue3的入口写法有哪些不同。
vue2
import Vue from 'vue' import App from './App.vue' new Vue({ => h(App)}).$mount('#app')
vue3
const { createApp } from 'vue' import App from "./src/App" createApp(App).mount(('#app')
vue官方实现的 createApp 会给我们的 template 映射生成 html 代码,但是要是你不想渲染生成到 html ,而是要渲染生成到 canvas 之类的不是html的代码的时候,那就需要用到 Custom Renderer API 来定义自己的 render 渲染生成函数了。
import { createApp } from "./runtime-render"; import App from "./src/App"; // 根组件 createApp(App).mount('#app');
使用自定义渲染API,如weex和myvue这类方案的问题就得到了完美解决。只需重写createApp即可。
按需编译,体积比vue2.x更小
框架的大小也会影响其性能。这是 Web 应用程序的唯一关注点,因为需要即时下载资源,在浏览器解析必要的 JavaScript 之前该应用程序是不可交互的。对于单页应用程序尤其如此。尽管 Vue 一直是相对轻量级的(Vue 2 的运行时大小压缩为 23 KB)。
在 Vue 3 中,通过将大多数全局 API 和内部帮助程序移至 ES 模块导出来,实现了这一目标。这使现代的打包工具可以静态分析模块依赖性并删除未使用的导出相关的代码。模板编译器还会生成友好的 Tree-shaking 代码,在模板中实际使用了该功能时才导入该功能的帮助程序。
框架的某些部分永远不会 Tree-shaking,因为它们对于任何类型的应用都是必不可少的。我们将这些必不可少的部分的度量标准称为基准尺寸。尽管增加了许多新功能,但 Vue 3 的基准大小压缩后约为 10 KB,还不到 Vue 2 的一半。
生命周期