1. vit原理
vite: https://github.com/vitejs/vite
面试题:谈谈你对vite的理解,最好对比webpack说明
webpack 原理图
webpack是打包所有模块后生成bundle文件,之后服务器访问bundle文件进行呈现;
这就导致修改某一个模块后需要全部重新打包
vite 原理图
vite没有打包过程,vite是直接启动一个服务器;
面试题答案:
webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。
由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite的优势越明显。
在HMR方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中不可以使用CommonJS
2. 效率的提升
客户端渲染效率比vue2提升了1.3~2倍
SSR渲染效率比vue2提升了2~3倍
面试题:vue3的效率提升主要表现在哪些方面?
- 静态提升
- 预字符串化
- 缓存事件处理函数
- Block Tree
- PatchFlag
2-1. 静态提升
预编译时: 生成render函数时
2-1-1. 静态节点会被提升
- 元素节点
- 没有绑定动态内容
1 | <h1>Hello World</h1> |
1 | // vue2 的静态节点 |
因为数据一发生改变就会运行render函数;而静态节点是不需要重复编译的;
2-1-2. 静态属性会被提升
1 | <div class="user"> |
1 | const hoisted = { class: "user" } |
2-2. 预字符串化
在模板编译中
1 | <div class="menu-bar-container"> |
当编译器遇到大量连续的静态内容,会直接将其编译为一个普通字符串节点
1 | const _hoisted_2 = _createStaticVNode("<div class=\"logo\"><h1>logo</h1></div><ul class=\"nav\"><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li><li><a href=\"\">menu</a></li></ul>") |
根据动静比,智能将大量的连续的静态标签编译为一个不同的字符串节点 (现阶段连续20个静态节点会进行预字符串化)
2-3. 缓存事件处理函数
在模板编译中
1 | <button @click="count++">plus</button> |
1 | // vue2 |
vue3 中认为事件基本是不会变化的,所以加入了缓存处理
2-4. Block Tree
vue2在对比新旧树的时候,并不知道哪些节点是静态的,哪些是动态的,因此只能一层一层比较,这就浪费了大部分时间在比对静态节点上
1 | <form> |
vue2 再节点对比式再用广度优先进行逐个对比;由于静态节点是不会发生变化的所以这样会浪费大量时间;
vue3 在预编译时,生成虚拟节点时会标记节点是静态节点、动态节点或哪部分为动态部分(PatchFlag),这样对比时只比较动态部分;
Block节点中记录了动态节点,对比时直接遍历即可
左block: [input1, input2]
右block: [input1, input2]
当树的某一个分支不稳定的时候,会生成一个新的块,即生成一个新的block节点;
2-5. PatchFlag
vue3针对每一个节点对比时进行了优化
vue2在对比每一个节点时,并不知道这个节点哪些相关信息会发生变化,因此只能将所有信息依次比对
1 | <div class="user" data-id="1" title="user name"> |
3. API和数据响应式的变化
面试题1:为什么vue3中去掉了vue构造函数?
面试题2:谈谈你对vue3数据响应式的理解
3-1. 去掉了Vue构造函数
在过去,如果遇到一个页面有多个vue
应用时,往往会遇到一些问题
1 | <!-- vue2 --> |
在vue3
中,去掉了Vue
构造函数,转而使用createApp
创建vue
应用
1 | <!-- vue3 --> |
更多vue应用的api:https://vuejs.org/api/application.html
3-2. 组件实例中的API
在vue3
中,组件实例是一个Proxy
,它仅提供了下列成员,功能和vue2
一样
属性:https://vuejs.org/api/component-instance.html
方法:https://vuejs.org/api/component-instance.html
3-3. 对比数据响应式
vue2和vue3均在相同的生命周期完成数据响应式,但做法不一样
在beforeCreate之后created之前完成数据响应式
3-4. 面试题参考答案
面试题1:为什么vue3中去掉了vue构造函数?
1 | vue2的全局构造函数带来了诸多问题: |
面试题2:谈谈你对vue3数据响应式的理解
1 | vue3不再使用Object.defineProperty的方式定义完成数据响应式,而是使用Proxy。 |
4. 模板中的变化
4-1. v-model
vue2
比较让人诟病的一点就是提供了两种双向绑定:v-model
和.sync
,在vue3
中,去掉了.sync
修饰符,只需要使用v-model
进行双向绑定即可。
为了让v-model
更好的针对多个属性进行双向绑定,vue3
作出了以下修改
当对自定义组件使用
v-model
指令时,绑定的属性名由原来的value
变为modelValue
,事件名由原来的input
变为update:modelValue
1
2
3
4
5
6
7
8
9
10
11
12<!-- vue2 -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
<!-- 简写为 -->
<ChildComponent v-model="pageTitle" />
<!-- vue3 -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
<!-- 简写为 -->
<ChildComponent v-model="pageTitle" />去掉了
.sync
修饰符,它原本的功能由v-model
的参数替代1
2
3
4
5
6
7
8
9<!-- 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
配置被移除
vue2 时用来修改v-model绑定的的属性名(value)和默认事件名称(input)
vue3 可以可以配置多个,所以移除了该配置
允许自定义
v-model
修饰符vue2 无此功能
注意:属性名称和方法名称是固定的
- 默认:
modelValue
和modelModifiers
- 自定义:
属性名
和属性名+Modifiers
使用:直接在
props
中声明即可代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23export default {
props: {
modelValue: Boolean,
text: String,
textModifiers: {
default: () => ({}),
},
},
setup(props, ctx) {
...
const handleTextChange = (e) => {
let value = e.target.value;
if (props.textModifiers && props.textModifiers.trim) {
value = value.trim();
}
ctx.emit("update:text", value);
};
return {
...
handleTextChange,
};
},
};- 默认:
4-2. v-if v-for
v-if
的优先级 现在高于 v-for
1 | <!-- vue2 --> |
4-3. key
当使用
<template>
进行v-for
循环时,需要把key
值放到<template>
中,而不是它的子元素中当使用
v-if v-else-if v-else
分支的时候,不再需要指定key
值,因为vue3
会自动给予每个分支一个唯一的key
即便要手工给予
key
值,也必须给予每个分支唯一的key
,不能因为要重用分支而给予相同的 key
4-4. Fragment
vue3
现在允许组件出现多个根节点
5. 组件变化
以下示例个功能为组件的加载方式(同步&异步)
1 | <!-- home.vue --> |
异步加载功能:
注意:以下代码为测试代码
1 | import { defineAsyncComponent, h } from "vue"; |
6. ReactivityApi
reactivity api: https://cn.vuejs.org/api/reactivity-utilities.html
vue3把数据响应式给暴露出来了,vue2中写在data中的数据会自动注入变成响应式数据;
1 | import {onMounted, onUnmounted, ref, computed, watchEffect} from "vue" |
在上述方法中:onMounted
、onUnmounted
属于 compositionApi
ref
、computed
、watchEffect
属于 ReactivityApi
6-1. 获取响应式数据
API | 传入 | 返回 | 备注 |
---|---|---|---|
reactive |
plain-object |
对象代理 |
深度代理对象中的所有成员 |
readonly |
plain-object or proxy |
对象代理 |
只能读取代理对象中的成员,不可修改 |
ref |
any |
{ value: ... } |
对value的访问是响应式的 如果给value的值是一个对象, 则会通过 reactive 函数进行代理如果已经是代理,则直接使用代理 |
computed |
function |
{ value: ... } |
当读取value值时, 会根据情况决定是否要运行函数 |
应用:
- 如果想要让一个对象变为响应式数据,可以使用
reactive
或ref
- 如果想要让一个对象的所有属性只读,使用
readonly
- 如果想要让一个非对象数据变为响应式数据,使用
ref
- 如果想要根据已知的响应式数据得到一个新的响应式数据,使用
computed
6-1-1. 笔试题1:下面的代码输出结果是什么?
1 | import { reactive, readonly, ref, computed } from "vue"; |
结果:
state ready
changed
fullname is Bi, Fang
fullname is Bi, Fang
false
true
Star Lucky
changed
fullname is Lucky, Star
fullname is Lucky, Star
false
6-1-2. 笔试题2:按照下面的要求完成函数
笔试题2:按照下面的要求完成函数
1 | function useUser(){ |
代码示例:
1 | import { reactive, readonly } from "vue"; |
6-1-3. 笔试题3:按照下面的要求完成函数
1 | function useDebounce(obj, duration){ |
代码示例:
1 | import { reactive, readonly } from "vue"; |
6-2. 监听数据变化
6-2-1. watchEffect
1 | const stop = watchEffect(() => { |
6-2-2. watch
1 | // 等效于vue2的$watch |
注意:无论是watchEffect
还是watch
,当依赖项变化时,回调函数的运行都是异步的(微队列)
应用:除非遇到下面的场景,否则均建议选择watchEffect
- 不希望回调函数一开始就执行
- 数据改变时,需要参考旧值
- 需要监控一些回调函数中不会用到的数据(检测新旧数据是否发生改变)
笔试题: 下面的代码输出结果是什么?
1 | import { reactive, watchEffect, watch } from "vue"; |
结果:
watchEffect 0
start
end
watchEffect 2
watch 2 0
time out
watchEffect 4
watch 4 2
6-3. 判断
API | 含义 |
---|---|
isProxy |
判断某个数据是否是由reactive 或readonly |
isReactive |
判断某个数据是否是通过reactive 创建的详细:https://v3.vuejs.org/api/basic-reactivity.html#isreactive |
isReadonly |
判断某个数据是否是通过readonly 创建的 |
isRef |
判断某个数据是否是一个ref 对象 |
6-4. 转换
6-4-1. unref
等同于:isRef(val) ? val.value : val
应用:
1 | function useNewTodo(todos){ |
6-4-2. toRef
得到一个响应式对象某个属性的ref格式
1 | const state = reactive({ |
6-4-2. toRefs
把一个响应式对象的所有属性转换为ref格式,然后包装到一个plain-object
中返回
1 | const state = reactive({ |
应用:
1 | setup(){ |
6-5. 降低心智负担
所有的composition function
均以ref
的结果返回,以保证setup
函数的返回结果中不包含reactive
或readonly
直接产生的数据
1 | function usePos(){ |
7. Composition Api
不同于reactivity api,composition api提供的函数很多是与组件深度绑定的,不能脱离组件而存在。
7-1. setup
1 | // component |
context对象的成员
成员 | 类型 | 说明 |
---|---|---|
attrs | 对象 | 同vue2 的this.$attrs |
slots | 对象 | 同vue2 的this.$slots |
emit | 方法 | 同vue2 的this.$emit |
7-2. 生命周期函数
vue2 option api | vue3 option api | vue 3 composition api |
---|---|---|
beforeCreate | beforeCreate | 不再需要,代码可直接置于setup中 |
created | created | 不再需要,代码可直接置于setup中 |
beforeMount | beforeMount | onBeforeMount |
mounted | mounted | onMounted |
beforeUpdate | beforeUpdate | onBeforeUpdate |
updated | 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有哪些优势?
从两个方面回答:
- 为了更好的逻辑复用和代码组织
- 更好的类型推导
1 | 有了composition api,配合reactivity api,可以在组件内部进行更加细粒度的控制,使得组件中不同的功能高度聚合,提升了代码的可维护性。对于不同组件的相同功能,也能够更好的复用。 |
8. 共享数据
8-1. vuex方案
在vue2中不适合中小型项目,
vue3中多了些新的选择,但是vuex还是比较复杂些,不适合中小型项目
安装vuex@4.x
两个重要变动:
- 去掉了构造函数
Vuex
,而使用createStore
创建仓库 - 为了配合
composition api
,新增useStore
函数获得仓库对象
代码示例[store]:
1 | // index.js |
1 | <!-- xxx.vue --> |
8-2. global state
由于vue3
的响应式系统本身可以脱离组件而存在,因此可以充分利用这一点,轻松制造多个全局响应式数据
代码示例:
1 | // store/useLoginUser 提供当前登录用户的共享数据 |
1 | <script> |
8-3. Provide&Inject
在vue2
中,提供了provide
和inject
配置,可以让开发者在高层组件中注入数据,然后在后代组件中使用
除了兼容vue2
的配置式注入,vue3
在composition api
中添加了provide
和inject
方法,可以在setup
函数中注入和使用数据
考虑到有些数据需要在整个vue应用中使用,vue3
还在应用实例中加入了provide
方法,用于提供整个应用的共享数据
1 | creaetApp(App) |
因此,我们可以利用这一点,在整个vue应用中提供共享数据
示例代码功能:在一个项目中存在多个vue实例;不同实例使用不同的共享数据
1 | // store/useLoginUser 提供当前登录用户的共享数据 |
8-4. 对比
vuex | global state | Provide&Inject | |
---|---|---|---|
组件数据共享 | ✅ | ✅ | ✅ |
可否脱离组件 | ✅ | ✅ | ❌ |
调试工具 | ✅ | ❌ | ✅ |
状态树 | ✅ | 自行决定 | 自行决定 |
量级 | 重 | 轻 | 轻 |
9. script setup
script setup 是一个语法糖,本身并没有带来任何新功能
轮播图部分代码示例:
1 | <script> |
setup语法糖示例:
1 | <script setup> |
区别:
传统方式(setup函数)中返回的所有方法和属性都会附着在组件实例上;父组件可以通过ref获取到组件实例进行调用方法和修改属性;可以通过expose
配置需要暴露的方法和属性,传递空则不会暴露任何方法;
script setup:组建中的方法和实例是不会附着在组件自身上的,可以通过defineExpose
配置需要暴露的方法和属性,无该配置则不会暴露任何属性和方法
__END__