doc: updat docs/vue.md (#943)

* 新增性能优化板块

* Move Performance Optimization introduction to ### 介绍 section

* 我在 `# 性能优化` 章节中又新增了三个优化方法:虚拟列表(Virtual Scrolling)、按需加载资源(Lazy Loading Resources)和优化事件监听(Event Delegation),每个方法都附带了代码示例和说明。这些方法可以进一步丰富优化策略,覆盖更多场景。
This commit is contained in:
fault
2025-03-22 09:22:28 +08:00
committed by GitHub
parent 17921854d3
commit 2e96576084

View File

@ -1529,10 +1529,356 @@ declare module 'vue' {
from: Route,
next: () => void
): void
}
}
```
## 性能优化
### 介绍
性能优化是构建高效 Vue 应用的关键。以下是一些特殊的优化策略,结合 Vue 的特性,可以大幅减少渲染开销、提升加载速度和用户体验。这些方法不仅限于单一 API而是从整体架构和开发实践出发提供通用的性能提升思路。
### 条件渲染与缓存结合
通过结合 `v-if` 和 `<KeepAlive>`,可以避免频繁销毁和重建组件,尤其是在切换视图或路由时。搭配 `defineAsyncComponent` 实现懒加载,进一步减少初次加载的开销。
```vue
<template>
<div>
<button @click="toggle">Toggle View</button>
<keep-alive>
<component :is="currentView" />
</keep-alive>
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue';
const currentView = ref('ViewA');
const toggle = () => {
currentView.value = currentView.value === 'ViewA' ? 'ViewB' : 'ViewA';
};
const ViewA = defineAsyncComponent(() => import('./ViewA.vue'));
const ViewB = defineAsyncComponent(() => import('./ViewB.vue'));
</script>
//<KeepAlive> 缓存动态组件,防止重复创建和销毁。
//defineAsyncComponent 实现组件懒加载,仅在需要时加载模块。
//效果:减少 DOM 操作和组件初始化的性能消耗,特别适合复杂组件切换或路由场景。
```
### 路由前置优化beforeRouteEnter
在路由进入前执行数据预取或条件检查,可以避免不必要的渲染和请求,提升页面加载效率。
```vue
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Profile',
beforeRouteEnter(to, from, next) {
// 模拟数据预取
fetchUserData(to.params.id).then((user) => {
next((vm) => {
vm.user = user; // 将数据传递给组件实例
});
}).catch(() => {
next(false); // 阻止路由进入
});
},
data() {
return {
user: null,
};
},
});
</script>
<template>
<div v-if="user">
<h1>{{ user.name }}</h1>
</div>
</template>
```
### 响应式对象的精简
避免将大型对象直接用 reactive 包裹,而是按需拆分,使用 ref 或 toRef 精细控制响应式范围,减少依赖追踪的开销。
```vue
<script setup>
import { ref, toRef, reactive } from 'vue';
const largeData = {
user: { name: 'Alice', age: 25 },
settings: { theme: 'dark', fontSize: 16 },
items: Array(1000).fill({ id: 0, value: 'test' }),
};
// 仅将需要的部分设为响应式
const userName = ref(largeData.user.name);
const settings = reactive(largeData.settings);
const firstItem = toRef(largeData.items[0], 'value');
</script>
<template>
<div>
<input v-model="userName" />
<p>Theme: {{ settings.theme }}</p>
<p>First Item: {{ firstItem }}</p>
</div>
</template>
```
### 计算属性的延迟执行
通过封装计算属性并结合 watchEffect实现按需计算避免不必要的开销。
```vuw
<script setup>
import { ref, computed, watchEffect } from 'vue';
const items = ref([]);
const filterText = ref('');
const filteredItems = computed(() => {
return items.value.filter((item) => item.includes(filterText.value));
});
watchEffect(() => {
if (filterText.value) {
// 仅在 filterText 不为空时触发计算
filteredItems.value;
}
});
</script>
<template>
<div>
<input v-model="filterText" placeholder="Filter items" />
<ul>
<li v-for="item in filteredItems" :key="item">{{ item }}</li>
</ul>
</div>
</template>
```
### v-memo 缓存子树
v-memo 用于缓存模板子树,仅在依赖项变化时更新,常用于优化列表或静态内容。
```vue
<template>
<div v-for="item in items" :key="item.id" v-memo="[item.updated]">
{{ item.name }} - {{ expensiveComputation(item) }}
</div>
</template>
<script setup>
import { ref } from 'vue';
const items = ref([
{ id: 1, name: 'Item 1', updated: false },
{ id: 2, name: 'Item 2', updated: false },
]);
const expensiveComputation = (item) => {
return item.name.toUpperCase(); // 模拟复杂计算
};
</script>
```
### 事件节流与防抖
通过在事件处理中引入节流throttle或防抖debounce减少高频事件的触发频率。
```vue
<script setup>
import { ref } from 'vue';
import { debounce } from 'lodash-es';
const searchText = ref('');
const search = debounce((value) => {
console.log('Search:', value); // 模拟搜索请求
}, 300);
const handleInput = (e) => {
searchText.value = e.target.value;
search(searchText.value);
};
</script>
<template>
<input
:value="searchText"
@input="handleInput"
placeholder="Type to search"
/>
</template>
```
### 虚拟列表Virtual Scrolling
对于长列表(如包含数千条数据的列表),可以使用虚拟列表技术,只渲染可视区域内的元素,减少 DOM 节点数量。
```vue
<template>
<div class="list-container" ref="list">
<div class="list-viewport" :style="{ height: totalHeight + 'px' }">
<div
class="list-content"
:style="{ transform: `translateY(${scrollTop}px)` }"
>
<div
v-for="item in visibleItems"
:key="item.id"
class="list-item"
>
{{ item.name }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
const items = ref(
Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
}))
);
const itemHeight = 40; // 每项高度
const viewportHeight = 400; // 视口高度
const totalHeight = computed(() => items.value.length * itemHeight);
const scrollTop = ref(0);
const visibleCount = Math.ceil(viewportHeight / itemHeight) + 2; // 多渲染2项作为缓冲
const visibleItems = computed(() => {
const start = Math.floor(scrollTop.value / itemHeight);
const end = Math.min(start + visibleCount, items.value.length);
return items.value.slice(start, end);
});
const list = ref(null);
const onScroll = () => {
scrollTop.value = list.value.scrollTop;
};
onMounted(() => {
list.value.addEventListener('scroll', onScroll);
});
onUnmounted(() => {
list.value.removeEventListener('scroll', onScroll);
});
</script>
<style>
.list-container {
height: 400px;
overflow-y: auto;
}
.list-item {
height: 40px;
line-height: 40px;
border-bottom: 1px solid #ddd;
}
</style>
```
### 按需加载资源Lazy Loading Resources
通过动态导入Dynamic Import按需加载非关键资源如图片、第三方库可以减少初次加载的开销提升首屏渲染速度。
```vue
<template>
<div>
<button @click="loadChart">Load Chart</button>
<div v-if="ChartComponent">
<ChartComponent :data="chartData" />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const ChartComponent = ref(null);
const chartData = ref([10, 20, 30, 40]);
const loadChart = async () => {
// 动态导入第三方库(如 Chart.js
const { default: Chart } = await import('chart.js/auto');
// 模拟动态加载的图表组件
ChartComponent.value = {
props: ['data'],
template: `<canvas ref="chart"></canvas>`,
mounted() {
new Chart(this.$refs.chart, {
type: 'bar',
data: {
labels: ['A', 'B', 'C', 'D'],
datasets: [{ data: this.data }],
},
});
},
};
};
</script>
```
### 优化事件监听Event Delegation
通过事件委托Event Delegation将事件监听器绑定到父元素减少直接绑定到每个子元素的事件监听器数量适合动态列表或大量元素场景。
```vue
<template>
<div class="item-list" @click="handleItemClick">
<div
v-for="item in items"
:key="item.id"
:data-id="item.id"
class="item"
>
{{ item.name }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const items = ref([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
]);
const handleItemClick = (event) => {
const itemId = event.target.dataset.id;
if (itemId) {
console.log(`Clicked item with ID: ${itemId}`);
}
};
</script>
<style>
.item-list {
padding: 10px;
}
.item {
padding: 10px;
border-bottom: 1px solid #ddd;
cursor: pointer;
}
</style>
```
API 参考
---