使用 vue3 的一些注意事项,让你系统性能有飞一样的提升
vue3 是一个是非优秀的框架,他的心智模型更加贴切 js 本身。并且官方就做了尽可能多的优化,让你不需要像写 react 那样,需要去考虑很多性能问题。
但是,vue3 也有一些需要注意的地方,这些地方如果处理不当,可能会导致系统性能下降。特别是我们在自定义 hooks 与封装 vue3 组件的时候,需要注意到这一点。
哪些老生常谈的点,就不再赘述了,这里只说一些容易被忽视的点。并且我们预设使用 Naive UI 作为组件库举栗子。
无意义的响应式数据
何为无意义的响应式数据?也就是说说该数据不会影响视图上的任何内容。
我们来看一些栗子:
有意义的响应式数据
<template>
<div>
<div>{{ count }}</div>
<button @click="countBtnClick">Increment</button>
</div>
</template>
<script setup>
const count = ref(0)
const countBtnClick = () => {
count.value++
}
</script>
上面这段代码是很典型的响应式数据代码。每当 count 发生变化时,vue 会重新渲染视图。
无意义的响应式数据
<template>
<NDataTable :columns="columns" :data="data" />
</template>
<script setup>
import { NDataTable } from 'naive-ui'
const columns = ref([
{
title: 'Name',
key: 'name',
},
{
title: 'Age',
key: 'age',
},
])
const data = ref([])
for (let i = 0; i < 10; i++) {
data.value.push({
name: 'John',
age: 20,
})
}
</script>
我们来观察这个代码的特点:
- 声明了一个
NDataTable组件,并且传入了columns和data两个属性。 - 动态的更新了
data的值。
有没有发现,columns 是无意义的响应式声明。也就是说,columns 是不会发生变化的,所以我们不需要使用 ref 来声明 columns。
而是把代码改为这样:
const columns = [ { title: 'Name', key: 'name', }, ]
当然了,这是一个很不起眼的栗子。并且有些人可能会疑问,就这么点数据,性能能差多少?
你说得对,但是,如果数据量很大呢?
声明了大量无意义的响应式数据
假设我们需要给 高德地图 注入海量的点位数据,那么我们可能会这样做:
// 假设这是一个用来注入海量点位的自定义 hooks
export const useMassMarks = () => {
const massMarkersInstance = ref(null)
const setMassMarks = (mk) => {
const p = mk.map((curr) => {
// 省略一些代码...
})
massMarkersInstance.value = new AMap.MassMarks(p, {
// 省略一些代码...
})
}
const getMassMarksInstance = () => {
return massMarkersInstance.value
}
const getMassMarks = () => {
return massMarks.value
}
return { setMassMarks, getMassMarksInstance, getMassMarks }
}
<script setup>
import { useMassMarks } from './useMassMarks'
const { setMassMarks, getMassMarksInstance, getMassMarks } = useMassMarks()
const p = []
for (let i = 0; i < 1000000; i++) {
p.push({
id: i,
position: [116.397428, 39.90923],
})
}
setMassMarks(p)
</script>
乍一看,这段代码好像没什么问题。但是:massMarksInstance 被响应式代理了 1000000 条数据,并且 massMarks 也被响应式代理了。
会导致整个渲染过程十分的卡顿。
这时候会有人说,可以用
shallowRef来声明数据。对,你说的确实没问题。但是这个就是栗子一中提到的无意义响应式数据。
当我们在做自定义 hooks 的时候,需要注意到这一点。如何避免无意义的响应式数据的声明。如果需要获取到一个数据,我们应该提供一个 get 方法来获取。
我们把 useMassMarks 改成这样:
export const useMassMarks = () => {
let massMarkersInstance = null
const setMassMarks = (mk) => {
const p = mk.map((curr) => {
// 省略一些代码...
})
massMarkersInstance = new AMap.MassMarks(p, {
// 省略一些代码...
})
}
const getMassMarksInstance = () => {
return massMarkersInstance
}
return { setMassMarks, getMassMarksInstance }
}
这样,massMarkersInstance 就不会被响应式代理了。并且整个地图的渲染过程会十分的流畅。
自定义组件
假设我们使用 interact.js 定义一个 Draggable 组件,并且传入一个 props 属性。
<template>
<div ref="draggableRef"></div>
</template>
<script setup>
import interact from 'interactjs'
const draggableRef = useTemplateRef('draggableRef')
const inst = shallowRef(null)
const expose = defineExpose()
expose()
onMounted(() => {
inst.value = interact(draggableRef.value).draggable({
// 省略实现代码...
})
})
onBeforeUnmount(() => {
inst.value?.destroy()
inst.value = null
})
</script>
是不是感觉这个代码没啥大毛病?但是,一个 Draggable 组件,并不需要响应式的 inst 实例。
这时候有人会说,你这里 expose 暴露了一个空对象出去,如果我需要拿到这个 inst 实例,我需要怎么办?
那么我们可以通过实现 get 方法来获取到 inst 实例,而不是靠响应式数据去获取。
<template>
<div ref="draggableRef"></div>
</template>
<script setup>
import interact from 'interactjs'
const draggableRef = useTemplateRef('draggableRef')
let inst = null
const expose = defineExpose()
const getInst = () => {
return inst
}
expose({ getInst })
onMounted(() => {
inst = interact(draggableRef.value).draggable({
// 省略实现代码...
})
})
onBeforeUnmount(() => {
inst?.destroy()
inst = null
})
</script>
总结
为什么需要去举例子这些?
因为本人最近在写地图相关的一些业务,在封装一些 hooks, components 的时候,一不小心踩到了这些坑的问题。
特别是海量点的相关 components, hooks,为啥我做出来的东西这么卡顿。
也是自己的技术水平不足,遇到了这些本不应该有的问题。并且在 vue3 官方也准确的提出了这些点,所以还是要多看文档才行(手动尴尬)。