1. 按需导入与代码分割
对于一些大型库,尽可能只导入需要的部分:
javascript
// 好的做法
import { Button, Select } from 'element-plus'
// 避免这样做
// import ElementPlus from 'element-plus'
代码分割是一种将应用分解成 分成较小的块 的技术,可以显著提高加载性能。在 Vue 中,我们可以结合动态导入和异步组件来实现:
配置路由:
javascript
// router.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// 路由级代码分割
component: () => import('./views/About.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
页面引入:
html
// App.vue
<template>
<div>
<!-- 其他内容 -->
<router-view></router-view>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
// 组件级代码分割
const AsyncComponent = defineAsyncComponent(() =>
import('./components/HeavyComponent.vue')
)
export default {
components: {
AsyncComponent
}
}
</script>
2. 确保 Props 稳定性
为了避免不必要的子组件更新,我们应该尽量保持传递给子组件的 props 稳定。这里有一个优化例子:
html
<!-- 优化前 -->
<template>
<ul>
<ListItem
v-for="item in items"
:key="item.id"
:item="item"
:is-selected="selectedId === item.id"
@select="selectItem"
/>
</ul>
</template>
<script>
export default {
data() {
return {
items: [/* ... */],
selectedId: null
}
},
methods: {
selectItem(id) {
this.selectedId = id
}
}
}
</script>
以下是代码优化后:
html
<!-- 优化后 -->
<template>
<ul>
<ListItem
v-for="item in itemsWithSelection"
:key="item.id"
:item="item"
@select="selectItem"
/>
</ul>
</template>
<script>
export default {
data() {
return {
items: [/* ... */],
selectedId: null
}
},
computed: {
itemsWithSelection() {
return this.items.map(item => ({
...item,
isSelected: item.id === this.selectedId
}))
}
},
methods: {
selectItem(id) {
this.selectedId = id
}
}
}
</script>
在优化后的版本中,我们将选择状态的计算移到了父组件的计算属性中。这样可以减少子组件的不必要更新,因为现在传递给子组件的 props 更加稳定。
3. v-once 和 v-memo 的使用
v-once 指令用于指定只需要渲染一次的内容。这对于静态内容特别有用:
html
<template>
<!-- 只渲染一次 -->
<h1 v-once>{{ title }}</h1>
</template>
v-memo 指令用于有条件地跳过组件或元素的更新,可以理解为只在给定状态数据变更时,更新视图:
html
<template>
<!-- 只在 `name` 改变时才更新 -->
<div v-memo="[name]">
<!-- 复杂的子树 -->
</div>
</template>
4. 大型虚拟列表
对于渲染大量数据的列表,可以使用虚拟列表技术。以下是使用 vue-virtual-scroller 的例子:
html
<template>
<RecycleScroller
class="scroller"
:items="list"
:item-size="32"
key-field="id"
v-slot="{ item }"
>
<div class="user">
{{ item.name }}
</div>
</RecycleScroller>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
export default {
components: {
RecycleScroller
},
data() {
return {
list: Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `User ${i}`
}))
}
}
}
</script>
<style>
.scroller {
height: 300px;
}
.user {
height: 32px;
padding: 0 12px;
display: flex;
align-items: center;
}
</style>
或者使用 VueUse 中的 useVirtualList
如果在真正大表格大数据量的情况下,我们需要考虑使用 Canvas table 方案。
5. 减少大型不可变数据的响应性开销
对于大型的、不经常变化的数据,可以使用 shallowRef 或 shallowReactive 来减少响应性开销:
javascript
import { shallowRef } from 'vue'
export default {
setup() {
const largeData = shallowRef([
// 大量数据...
])
const updateData = () => {
// 错误:不会触发更新
// largeData.value.push(newItem)
// 正确:替换整个引用以触发更新
largeData.value = [...largeData.value, newItem]
}
return {
largeData,
updateData
}
}
}
使用 shallowRef 可以显著减少大型数据结构的响应性开销,但要注意,这意味着只有顶层属性的变化会触发更新,一定要确保视图的更新不依赖于下层数据。
6. 避免不必要的组件抽象
虽然组件抽象可以提高代码的可维护性,但过度的抽象可能导致性能问题。 特别是在渲染大列表时,应该谨慎使用小型的、功能单一的组件。
html
<!-- 优化前:每个项都是一个单独的组件 -->
<template>
<ul>
<ListItem
v-for="item in items"
:key="item.id"
:item="item"
/>
</ul>
</template>
<!-- 优化后:整个列表作为一个组件 -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
<!-- 其他项目内容 -->
</li>
</ul>
</template>
<script>
export default {
props: {
items: Array
}
}
</script>