Vue3.js
Vue3的重大变化

1 | import { ref } from "vue"; |

vite原理
webpack原理图

vite原理图

面试题:说说
webpack
和vite
的区别
webpack
会先打包,然后启动开发服务器,请求服务器时,直接给予打包结果.而
vite
是直接启动开发服务器,请求哪个模块再对该模块进行实时编译.由于现代浏览器本身支持
ES Module
,会自动向依赖的Module发出请求.vite
充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack
那样进行打包合并.由于
vite
在启动的时候不需要打包,也就意味着不需要分析模块依赖,不需要编译,因此,启动速度非常快.当浏览器请求某个模块时,再根据需要对模块内容进行编译.这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂,模块越多,
vite
的优势越明显.在HMR(Hot Module Replace)方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像
webpack
那样需要把该模块的相关依赖全部编译一次,效率更高.当需要打包到生产环境时,
vite
使用传统的rollup
进行打包,因此,vite
的主要优势在开发阶段.另外,由于vite
利用的是ES Module
,因此代码中不能使用CommonJS
效率提升
CSR效率提高了1.3到2倍
SSR效率提高了2-3倍
静态提升
下面的静态节点会被提升
- 元素节点
- 没有绑定动态内容
1 | // vue2的静态节点 |
静态属性会被提升
1 | <div class="user"> |
1 | const hoisted = { class: "user" } |
预字符串化
1 | <div class="menu-bar-container"> |
当编译器遇到大量连续的静态内容,会直接将其编译为一个普通字符串节点.
1 | const _hoisted_2 = _createStaticVNode("x <div class="menu-bar-container"> <div class="logo"> <h1>logo</h1> </div> <ul class="nav"> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> </ul> <div class="user"> <span> {{user.name}} </span> </div></div>") |


缓存事件处理函数
1 | <button @click="count++"> |
1 | // vue2 |
Block Tree
vue2在对比新旧树的时候,并不知道哪些节点是静态的,哪些是动态的,因此只能一层一层的比较,这就浪费了大部分的时间在对比静态节点上
1 | <form> |


PatchFlag
vue2在对比每一个节点时,并不知道这个节点哪些相关信息回发生变化,因此只能将所有信息依次对比
1 | <div class="user" data-id="1" title="user name"> |

API和数据响应式的变化
面试题1: 为什么vue3中去掉了vue构造函数
面试题2: 谈谈你对vue3数据响应式的理解
去掉了Vue的构造函数
在过去,如果遇到一个页面有多个vue
应用,往往会遇到一些问题
1 | <!-- vue2 --> |
在vue3
中,去掉了Vue
构造函数,转而使用createApp
创建vue
应用.
1 | <!-- vue3 --> |
面试题1: 为什么
vue3
中去掉了vue
构造函数
- 调用构造函数的静态方法会对所有
vue
应用都生效,不利于隔离不同应用.vue2
的构造函数集成了太多功能,不利于tree shaking
,vue3
把这些功能使用普通函数导出,能够充分利用tree shaking
优化打包体积.vue2
没有把==组件实例==和==vue应用==两个概念区分开来,在vue2
中,通过new Vue
创建的对象,既是一个vue应用,同时又是一个特殊的vue组件.vue3
中,把两个概念区分开来,通过createApp创建对象,是一个vue应用,它内部提供的方法是针对整个应用的,而不再是一个特殊的组件.面试题2: 谈谈你对vue3数据响应式的理解
vue3不再使用
Object.defineProperty
的方式定义完成数据响应式,而是使用Proxy
.除了
Proxy
本身效率比Object.defineProperty
更高之外,由于不必递归遍历所有属性,而是直接得到一个Proxy
.所以在vue3
中,对数据的访问是动态的,当访问某个属性的时候,再动态的获取和设置,这就极大地提升了在组件初始阶段的效率.同时,由于
Proxy
可以监控到成员的新增和删除,因此,在vue3
中新增成员,删除成员,索引访问等均可以触发重新渲染,而这些在vue2
中是难以做到的.
组件实例中的API
在vue3
中,组件实例是一个Proxy
,它仅仅提供了下列成员,功能和vue2
一样
对比数据响应式
vue2
和vue3
均在相同的生命周期完成数据响应式,但做法不一样.


指令
v-model
为了让v-model
更好的针对多个属性进行双向绑定,vue3
作出了以下修改.
当对自定义组件使用
v-model
指令时,绑定的属性名由原来的value
变为modelValue
,事件名由原来的input
变为update:modelValue
1
2
3
4
5
6
7
8
9
10
11
12
13<!-- vue2 -->
<ChildComponent :value="pageTitle" @input="pageTitle=$event" />
<!-- 简写为 -->
<ChildComponent v-model="pageTitle" />
<!-- vue3 -->
<ChildComponent
:modelValue="pageTitle"
@input:modelValue="pageTitle = $event"
/>
<!-- 简写为 -->
<ChildComponent v-model="pageTitle" />
去掉了
.sync
修饰符,它原本的功能由v-model
的参数替代1
2
3
4
5
6
7
8
9
10<!-- vue2 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
<!-- 简写为 -->
<ChildComponent :title.sync="pageTitle" />
<!-- vue3 -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
<!-- 简写为 -->
<ChildComponent v-model:title="pageTitle" />model
配置被移除允许自定义
v-model
修饰符
vue2无此功能

v-if 和 v-for
v-if
的优先级高于v-for
key
当使用<template>
进行v-for
循环的时候,需要把key
值放到<template>
中,而不是它的子元素中.
当使用v-if v-else-if v-else
分支的时候,不再需要指定key
值,因为vue3
会自动给予每个分支一个唯一的key
即便要手工给予key
值,也必须给予每个分支唯一的key
,不能因为要重用分支而给予相同的key
Fragment
组件的变化
响应式数据
获取响应式数据
API | 传入 | 返回 | 备注 |
---|---|---|---|
reactive |
plain-object |
对象代理 | 深度代理对象中的所有成员 |
readonly |
plain-object OR proxy |
对象代理 | 只能读取代理对象中的成员,不可修改 |
ref |
any |
{value: ...} |
对value的访问是响应式的,如果给value的值是一个对象,则会通过reactive 函数进行代理,如果已经是代理,则直接使用代理 |
computed |
`function`` | {value: ...} |
当读取value值时,会根据情况决定是否要运行函数 |
应用:
- 如果想要让一个对象变为响应式数据,可以使用
reactive
或ref
- 如果想要让一个对象的所有属性只读,使用
readonly
- 如果想要让一个非对象数据变为响应式数据,使用
ref
- 如果想要根据已知的响应式数据得到一个新的响应式数据,使用
computed
笔试题1: 下面代码的输出结果是什么
1 | import { computed, reactive, readonly, ref } from 'vue' |
笔试题2: 按照下面的要求完成函数
1 | import { computed, reactive, readonly, ref } from 'vue' |
笔试题3: 按照下面的要求完成函数
1 | import {reactive, readonly} from 'vue'; |
监听数据变化
watchEffect
1 | const stop = watchEffect(()=>{ |
1 | import { reactive, ref, watchEffect } from 'vue'; |
watch
1 | // 等效于vue2的$watch |
1 | import { reactive, ref, watch } from 'vue'; |
1 | watch([()=> state.a, count], ([new1, new2], [old1, old2]) => { |
注意: 无论是
watchEffect
还是watch
,当依赖项变化时,回调函数的执行都是异步的(微队列)
应用: 除非遇到下面的场景,否则均建议选择选择watchEffect
- 不希望回调函数一开始就执行
- 数据改变时,需要参考旧值
- 需要监控一些回调函数中不会用到的数据
笔试题: 下面的代码的输出结果是什么?
1 | import { reactive, watchEffect, watch } from 'vue'; |
判断
API | 含义 |
---|---|
isProxy |
判断某个数据是否是由reactive 或readonly 创建的 |
isReactive |
判断某个数据是否是通过reactive 创建的 |
isReadonly |
判断某个数据是否是通过readonly 创建的 |
isRef |
判断某个数据是否是一个ref 对象 |
转换
unref
等同于isRef(val) ? val.value : val
应用:
1 | function useNewTodo(todos) { |
toRef
得到一个响应式对象==某个属性==的ref格式
1 | const state = reactive({ |
toRefs
把一个响应式对象的所有属性转换为ref
格式,然后包装到一个plain-object
中返回
1 | const state = reactive({ |
应用
1 | setup() { |
降低心智负担
所有的composition function
均以ref
的结果返回,以保证setup
函数的返回结果中不包含reactive
或readonly
直接产生的数据.
1 | import { reactive, toRefs, ref, readonly } from 'vue' |
组合式API
面试题: composition api相比于 option api 有哪些优势?
不同于reactivity api
,composition api
提供的函数很多都是与组件深度绑定的,不能脱离组件而存在.
setup
1 | // component |
context
对象的成员
成员 | 类型 | 说明 |
---|---|---|
attrs | 对象 | 同vue2 的this.$attrs |
slots | 对象 | 同vue2 的this.$slots |
emit | 方法 | 同vue2 的this.$emit |
生命周期函数
vue2 option api | vue3 option api | vue3 composition api |
---|---|---|
beforeCreate | beforeCreate | 不再需要,代码可直接置于setup中,因为数据响应式API被单独抽离 |
created | created | 不再需要,代码可直接置于setup中 |
beforeMount | beforeMount | onBeforeMount |
mounted | mounted | onMounted |
beforeUpdate | beforeUpdate | onBeforeUpdate |
upodated | updated | onUpdated |
beforeDestroy | ==beforeUnmount== | onBeforeUnmount |
destroyed | ==unmounted== | onUnmounted |
errorCaptured | errorCaptured | onErrorCaptured |
- | ==renderTracked== | onRenderTracked |
- | ==renderTriggered== | onRenderTriggered |
新增钩子函数说明
钩子函数 | 参数 | 执行时机 |
---|---|---|
renderTracked | DebuggerEvent | 渲染vdom收集到的每一次依赖时 |
renderTriggered | DebuggerEvent | 某个依赖变化导致组件重新渲染时 |
DebuggerEvent
- target: 跟踪或触发渲染的对象
- key: 跟踪或触发渲染的属性
- type: 跟踪或触发渲染的方式
面试题参考答案
面试题: composition api
相比于option api
有哪些优势
从两个方面回答:
- 为了更好的逻辑复用和代码组织
- 更好的类型推导
有了
composition api
,配合reactivity api
,可以在组件内部进行更加细粒度的控制,使得组件中不同的功能高度聚合,提升了代码的可维护性.对于不同组件的相同功能,也能够更好的复用相比于
option api
,composition api
中没有了奇怪的this
,所有的api
变得更加的函数式,这有利于和类型推导系统比如TS的深度配合.
共享数据
vuex方案
- 去掉了构造函数
Vuex
,而使用createStore
创建仓库 - 为了配合
composition api
,新增useStore
函数获得仓库对象
global state
由于vue3
的响应式系统本身可以脱离组件而存在,因此可以充分利用这一点,轻松制造多个全局响应式数据.

Provide&Inject
在vue2
中,提供了provide
和inject
配置,可以让开发者在高层组件中注入数据,然后在后代组件中使用.

除了兼容vue2
的配置式注入,vue3
在组合式API中,添加了provide
和inject
方法,可以在setup
函数中注入和使用数据

考虑到有些数据需要在整个vue
应用中使用,vue3
还在应用实例中加入了provied
方法,用于提供整个应用的共享数据.
1 | createApp(App) |

因此,我们可以利用这一点,在整个vue
应用中提供共享数据.
对比
vuex | global state | Provide&Inject | |
---|---|---|---|
组件数据共享 | √ | √ | √ |
可否脱离组件 | √ | √ | × |
调试工具 | √ | × | √ |
状态树 | √ | 自行决定 | 自行决定 |
量级 | 重 | 轻 | 轻 |