Vue3小计
LzMiracle 前端鼓励师

Vue3相对于Vue2 的提升

  1. 性能提升

1、diff 算法优化

Vue2

虚拟dom是进行全量的对比

Vue3

新增了静态标记,与上次虚拟节点比较时,只比较那些有带有静态标记(PatchFlag)的节点。并且可以通过flag的信息得知当前节点要对比的具体内容

2、静态提升

Vue2中无论元素是否参与更新,每次都会重新创建,然后在渲染

Vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用

3、 事件监听缓存

默认情况下onClick会被视为动态绑定,所以每次都会区追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可

1. v-for上ref的绑定

需要把ref绑定到函数上,不再是数组的属性,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<div v-for="item in list" :ref="setItemRef"></div>
// Vue2
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemRef(el) {
this.itemRefs.push(el)
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}
// Vue 3
import { ref, onBeforeUpdate, onUpdated } from 'vue'

export default {
setup() {
let itemRefs = []
const setItemRef = el => {
itemRefs.push(el)
}
onBeforeUpdate(() => {
itemRefs = []
})
onUpdated(() => {
console.log(itemRefs)
})
return {
itemRefs,
setItemRef
}
}
}

2. v-on的修饰符.native移除

v-on的.native修饰符移除,更改为允许子组件定义真正会被触发的事件

1
2
3
4
5
6
7
8
9
10
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
// MyComponent.vue
<script>
export default {
emits: ['close']
}
</script>

3. 同时使用v-if和v-for优先级改变

Vue2中v-for的优先级是大于v-if,现在Vue3中 v-if的优先级大于v-for

4. 组合式API

由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态计算属性方法

Composition API的本质, 注入 API

setup函数只能是同步的

setup执行时机:setup ->beforeCreate -> created

setup 选项应该是一个接受 propscontext 的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
带ref的响应式变量
setup (props) {
let repositories = []
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}

return {
repositories,
getUserRepositories // 返回的函数与方法的行为相同
}
}
// 这里的 repositories 变量是非响应式的

// 用ref定义
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}

return {
repositories,
getUserRepositories
}
},
// 这里的repositories就是响应式的变量 赋值的话是repositories.value
// 因为props是响应式的所以不能用ES6解构,它会消除props的响应性。如果需要解构,要用setup函数的toRefs

5. ref、reactive、toRef、toRefs、shallowRef、shallowReactive

ref:函数接收一个基本数据类型的参数同时返回一个基于该值的响应性对象,该对象内部有且仅有一个属性 value

reactive: 函数接收一个复杂数据类型的数据(对象或数组)作为参数,并返回一个响应式代理对象,如果给它传递其它对象,默认下修改对象界面不会更新,如果想界面更新,必须重新赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<h3>{{ temp }}</h3>
<p>{{ user.name }}</p>
<p>{{ user.age }}</p>
<button @click="increase">click me!</button>
</div>
</template>
import { ref, reactive } from 'vue'
export default {
setup() {
const temp = ref(0)
temp.value = 'hello'
const user = reactive({ name: 'lemon', age: 20 })
console.log(temp) // 打印出来一个ref对象
console.log(temp.value) // hello
console.log(user) // Proxy {name:'lemon',age:20}

const increase = () => {
user.age++
}
return { temp, user, increase }
}
}

toRef : 是将个对象 A 中的某个属性 x 转换为响应式数据,其接收两个参数,第一个参数为对象 A,第二个参数为对象中的某个属性名 x

与ref的不同

  1. 参数不同:ref()接收一个 js 基本数据类型的参数;toRef()接收两个参数,第一个为对象,第二个为对象中的某个属性;
  2. 原理不同:ref()是对原数据的一个深拷贝,当其值改变时不会影响到原始值;toRef()是对原数据的一个引用,当值改变时会影响到原始值;
  3. 响应性不同:ref()创建的数据会触发 vue 模版更新;toRef()创建的响应式数据并不会触发 vue 模版更新,所以toRef()的本质是引用,与原始数据有关联

toRefs : 当我们希望对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据相关联,并且更新响应式数据时不更新界面,这时候toRefs()就派上用场了,它用于批量设置多个响应式数据, 可以用来解构props

shallowRef: 非递归监听 如果是通过shallowRef创建的数据,那么Vue监听的是.value的变化并不是第一层的变化, 本质是shallowReactive({value: xxx}), 所以value才是第一层

shallowReactive: 非递归监听 只监听第一层的变化

triggerRef: 更新界面修改的值 Vue3并没有提供triggerReactive, 所以reactive是无法触发界面更新的

6. Mixin 变为浅层次合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// mixin.js
export default {
data() {
return {
user: {
name: 'lzh',
id: 2
}
}
},
}

Vue2的mixin
// Component.vue
export default {
data() {
return {
user: {
id: 1
}
}
},
console.log(this.user) // Object {name: 'lzh', id: 1}
}

Vue3的mixin
// Component.vue
export default {
data() {
return {
user: {
id: 1
}
}
},
console.log(this.user) // Proxy {id: 1}
}

7. 过滤器移除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Vue2
<template>
<h1>Bank Account Balance</h1>
<p>{{ accountBalance | currencyUSD }}</p>
</template>

<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
filters: {
currencyUSD(value) {
return '$' + value
}
}
}
</script>

// Vue3 不在支持过滤器,使用计算属性代替
<template>
<h1>Bank Account Balance</h1>
<p>{{ accountInUSD }}</p>
</template>

<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>

8.toRaw、markRaw

toRaw: 为了方便修改那些不需要被追踪、不需要更新UI界面的数据,其原理是拿到原始数据,如果想的到通过ref获取的响应数据的原始数据,需要通过.value

markRaw: 使得数据不被追踪

9. customRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// coutomRef 返回一个ref对象, 可以显示的追踪和触发响应
// 自定义一个Ref
function myRef() {
return customRef((track, trigger) => {
return {
get() {
track() // 告诉Vue这个数据需要追踪变化
return value
},
set(newValue) {
value = newValue
trigger() // 告诉Vue这个数据需要触发界面更新
}
}
})
}
// es6的一个 方法 会返回一个promise
fetch('../data.json').then(res => {
return res.json();
}).then(data => {
console.log(data) // 值
}).catch(err => {
console.log(err)
})

10. readonly、、isReadonly、shallowReadonly

1
2
3
4
5
// readonly 创建一个只读的数据 递归只读
// shallowReadonly 第一层是只读的 但不是递归只读 修改了其它层也不会更新ui界面
// const 和 readonly的区别
const 赋值保护 不能重新给变量赋值 但是变量里面的属性可以
readonly 属性保护 不能给属性重新赋值

11. Vue3响应式数据的实现

通过Proxy实现

12. 手写shallowReactive、shallowRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function myShallowReactive(obj) { // 监听第一层的变化
return Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, val) {
obj[key] = value
console.log('更新UI界面')
return true
}
})
}
let obj = {
a: '1',
gf: {
b: '2',
c: {
d: '3',
e: {
f: '4'
}
}
}
}
let state = myShallowReactive(obj)
state.a = 'a'
state.gf.b = 'b'
state.gf.c.d = 'd'
state.gf.c.e.f = 'f' // 这样就只触发了一次
function myShallowRef(val) {
return myShallowReactive({value: val})
// 只能监听value的变化 不能监听value里面的属性的变化
}
let state = myShallowRef(obj)
state.value = {
a: '1',
gf: {
b: '2',
d: {
c: '3',
d: {
e: '4'
}
}
}
} // 这样就只会触发一次

13. 手写Reactive、Ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Reactive
function Reactive(obj) {
if(typeof obj === 'object') {
// 如果是一个数组,那么取出数组的每一个元素,判断元素是否又是一个对象,如果是就需要包装成Proxy
if(obj instanceof Array) {
obj.forEach( (item, index) => {
if(typeof item === 'object') {
obj[index] = Reactive(item)
}
})
}
else {
// 如果是一个对象,那么取出对象属性的值,判断对象属性的取值是否又是一个对象,如果是一个对象,那么也需要包装成Proxy
for(let key in obj) {
let item = obj[key]
if(typeof item === 'object') {
obj[key] = Reactive(item)
}
}
}
}
else {
console.warn(`${obj} is not a object`)
}
return Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, val) {
obj[key] = value
console.log('更新UI界面')
return true
}
})
}
// Ref
function Ref(val) {
return Reactive({value: val})
}

14. 手写shallowReadonly、readonly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// shallowReadonly
function myShallowReadonly(obj) { // 监听第一层的变化
return Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, val) {
// obj[key] = value
// console.log('更新UI界面')
// return true
console.warn(`${obj} 是只读的属性`)
}
})
}

function Readonly(obj) {
if(typeof obj === 'object') {
// 如果是一个数组,那么取出数组的每一个元素,判断元素是否又是一个对象,如果是就需要包装成Proxy
if(obj instanceof Array) {
obj.forEach( (item, index) => {
if(typeof item === 'object') {
obj[index] = Reactive(item)
}
})
}
else {
// 如果是一个对象,那么取出对象属性的值,判断对象属性的取值是否又是一个对象,如果是一个对象,那么也需要包装成Proxy
for(let key in obj) {
let item = obj[key]
if(typeof item === 'object') {
obj[key] = Reactive(item)
}
}
}
}
else {
console.warn(`${obj} is not a object`)
}
return Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, val) {
// obj[key] = value
// console.log('更新UI界面')
console.warn(`${obj} 是只读属性`)
return true
}
})
}
 评论