文章目录
- 前言
- 一、Composition Api
- 二、setup语法糖
- 三、响应式
- ref
- reactive
- 四、其他一些关键点
- v-pre
- v-once
- v-memo
- v-cloak
- 五、虚拟Dom
- 五、diff算法
前言
本文用于记录学习Vue3的过程
一、Composition Api
我觉得首先VUE3最大的改变就是对于代码书写的改变,从原来选择式API变成现在的组合式API方式,Vue3也是支持选择式的,他能让我们的代码逻辑不再满屏跳转,让代码更具有逻辑性,当然我觉得也更好调试了。
二、setup语法糖
< script setup>
是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。
当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:
- 更少的样板内容,更简洁的代码。
- 能够使用纯 TypeScript 声明 props 和自定义事件。
- 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
- 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。
三、响应式
Vue3中响应式是整个框架的核心,要使用响应式就须通过 ref 或者 reactive将常量变为响应式
其中可以搭配下列API简化开发(在之后的文章中介绍)
computed()
readonly()
watchEffect()
watchPostEffect()
watchSyncEffect()
watch()
在进阶响应式还有其他响应式API
ref
ref 可以绑定一些基本数据类型,比如字符串、数字等。ref实际上是通过对象自身value属性的get和set方法来拦截value属性达到响应式的。
所以使用ref定义的数据的时候,都是以 xxx.value来使用。
使用ref也是有好处的,在将其封装到一个对象中的时候,所有的基本数据类型格式都是统一的,在数据传输的时候传输的也是对象地址,而不是值,这保证了数据的统一性。
ref:
深层次的响应式,一般用来做普通类型的响应式如,除arr\object的数据类型,底层arr和object实现也是走reactive
可以获取dom元素具体使用场景在 组件通讯–>06_ref-children-parent中
isRef:
用来判断某个对象是否为响应式对象 传入参数:变量,返回结果为:boolean
shallowRef:
浅层次的响应式(只会在响应到 xxx.value)
第三方库对象代理不能用proxy时可以用这个
注:不能和ref混用,当两者变量同时出现在template中shallowRef会被影响视图的更新
ref底层调用triggerRefValue–>triggerEffects 使得强制更新收集的依赖,
triggerRef:
强制更新收集的依赖
customRef:
自定义响应式,浅层次的响应式
reactive
reactive:
对类型进行了约束,不能绑定普通数据类型,只能绑定引用类型:Array Object Map Set
reactive proxy不能直接赋值,否则破坏响应式对象
解决:数组 可以push加解构
添加对象将数组当成属性使用
readonly:
用来将其变为只读的
注:与 reactive一起使用时没有作用
shallowReactive:
浅层响应式,与shallowRef类似,存在相同的问题和reactive一起使用会影响响应
四、其他一些关键点
v-pre
跳过该标签以及子标签的编译按照原内容显示
<span v-pre>{{ this will not be compiled }}</span>
v-once
使用该命令可以让该标签以及子标签只渲染一次,并在未来跳过更新。
<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 带有子元素的元素 -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!-- 组件 -->
<MyComponent v-once :comment="msg" />
<!-- `v-for` 指令 -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
使用场景:
- 不需要数据响应式的标签上
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 v-once>{{ msg }}</h1>
<input v-model="msg">
</template>
v-memo
v-memo也具有v-once的功能与v-once不同的是,v-memo可以传 条件,能更好的控制模板的缓存,在其中可以传一个逻辑,也可以传一个变量来控制。
// 当valueA和valueB不变的时候不更新
<div v-memo="[valueA, valueB]">
...
</div>
// 当item.id === selected条件成立不更新
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
值得注意的是如果不传任何东西,效果和v-once一样。
当搭配 v-for 使用 v-memo,确保两者都绑定在同一个元素上。v-memo 不能用在 v-for 内部。
v-cloak
用于隐藏尚未完成编译的 DOM 模板
使用场景
- 在数据未完全挂载到实例上时,会出现如v-pre指令的原始内容效果的”未编译模板闪现“的情况
搭配 [v-cloak] { display: none } css属性,可以避免这种情况,v-cloak会一直挂载在标签上直到组件编译完成前都隐藏原始模板,最后他会自动移除。
五、虚拟Dom
虚拟Dom的存在大大降低了 操作真实DOM的代价,因为一个真实DOM结点上的属性有很多,通过对虚拟DOM的预操作让真实DOM以最小的代价更新视图。虚拟DOM是JS对象,里面储存了关于结点的信息。
真实DOM属性
<div></div>
虚拟DOM结点
<div id="app">
<h1>hello world!</h1>
</div>
对应的虚拟DOM为
{
tagName: 'div',
props: {
id: 'app'
},
children: [
{
tagName: 'h1',
props: {},
children: ['hello world!']
}
]
}
五、diff算法
diff算法的目的就是用来以最少的代价操作DOM,就是能复用的绝不新建,以最少的移动操作DOM。
Vue3中diff算法相比Vue2来说,有了很大的提升,主要靠他最长增长子序列算法来提升移动Dom代价。
Vue2更新结点的操纵主要有(同级对比,深度优先遍历)
- 如果新节点有子节点而老节点没有子节点,则判断老节点是否有文本内容,如果有就清空老节点的文本内容,然后为其新增子节点。
- 如果新节点没有子节点而老节点有子节点,则先删除老节点的子节点,然后设置文本内容。
- 如果新节点没有子节点,老节点也没有子节点,则进行文本的比对,然后设置文本内容。
- 如果新节点有子节点,老节点也有子节点,则进行新老子节点的比对,然后进行新增、移动、删除的操作,这就是diff 算法发生的地方。
Vue2中是通过双端对比算法来进行DOM的操作,主要操作有:
- 头(新)<===>头(旧)
- 尾(新)<===>尾(旧)
- 头(旧)<===>尾(新)
- 尾(新)<===>头(旧)
在比较过程中如果存在相同就移动复用,出现多的或者少的就进行新增和删除操作。
最后对于旧DOM中多的没有比对的就删除,新DOM没有比对的就新增。
Vue3中,分为两种情况,有key和没有key的标识,key也就是标签上的key,大多在使用v-for时,key比较常见。
- 没有key
没有Key时,,总共分为三步
-
- 头(新)<----> 头(旧) 从左到右依次比对,相同就直接复用,一直到不同 - 尾(新)<----> 尾(旧) 从右到左依次比对,相同就直接复用,一直到不同 - 更新和删除操作 剩余结点如果多了就删除,少了就新增。(删除在前,新增在后)
-
有key
有key时,会经过五步,,其中最重要的为第五步,也是运用了最长递增子序列算法的步骤
-
- 前序比较
从左到右依次比对,相同就直接复用,一直到不同 - 后序比较
从右到左依次比对,相同就直接复用,一直到不同 - 检查新增
有需要新增则新增 - 检查删除
有需要删除则删除 - 剩余结点数新旧一样 (无序)特殊处理
- 构建新结点的映射关系
- 新增或者删除结点
- move为true求最长递增子序列
得到移动结点的最小代价,时间复杂度为O(nlogn) - 移动不在序列里的结点
- 前序比较
最长递增子序列:
// 贪心+二分
function getSequence(arr: number[]): number[] {
const p = arr.slice() // 保存原始数据
const result = [0] // 存储最长增长子序列的索引数组
let i, j, u, v, c
const len = arr.length
for (i = 0; i < len; i++) {
const arrI = arr[i]
if (arrI !== 0) {
j = result[result.length - 1] // j是子序列索引最后一项
if (arr[j] < arrI) { // 如果arr[i] > arr[j], 当前值比最后一项还大,可以直接push到索引数组(result)中去
p[i] = j // p记录第i个位置的索引变为j
result.push(i)
continue
}
u = 0 // 数组的第一项
v = result.length - 1 // 数组的最后一项
while (u < v) { // 如果arrI <= arr[j] 通过二分查找,将i插入到result对应位置;u和v相等时循环停止
c = (u + v) >> 1 // 二分查找
if (arr[result[c]] < arrI) {
u = c + 1 // 移动u
} else {
v = c // 中间的位置大于等于i,v=c
}
}
if (arrI < arr[result[u]]) {
if (u > 0) {
p[i] = result[u - 1] // 记录修改的索引
}
result[u] = i // 更新索引数组(result)
}
}
}
u = result.length
v = result[u - 1]
//把u值赋给result
while (u-- > 0) { // 最后通过p数组对result数组进行进行修订,取得正确的索引
result[u] = v
v = p[v]
}
return result
}
所以对于key的运用一定程度上能提升性能。