虚拟列表与响应式优化
约 1039 字大约 3 分钟
布欧-Lewyon
2026-05-15
虚拟列表
当渲染大量列表数据(10000+ 条)时,DOM 节点过多会导致页面卡顿。虚拟列表只渲染可见区域内的元素。
vue-virtual-scroller
npm install vue-virtual-scroller@next<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
const items = ref(Array.from({ length: 10000 }, (_, i) => ({
id: i,
text: `Item ${i + 1}`
})))
</script>
<template>
<RecycleScroller
class="scroller"
:items="items"
:item-size="50"
key-field="id"
v-slot="{ item }"
>
<div class="item">{{ item.text }}</div>
</RecycleScroller>
</template>
<style scoped>
.scroller { height: 400px; }
.item { height: 50px; line-height: 50px; }
</style>手动实现简单虚拟列表
<!-- VirtualList.vue -->
<script setup>
import { ref, computed, onMounted } from 'vue'
const props = defineProps({
items: { type: Array, required: true },
itemHeight: { type: Number, default: 50 }
})
const containerRef = ref(null)
const scrollTop = ref(0)
const containerHeight = ref(400)
const visibleCount = computed(() =>
Math.ceil(containerHeight.value / props.itemHeight) + 2
)
const startIndex = computed(() =>
Math.floor(scrollTop.value / props.itemHeight)
)
const visibleItems = computed(() =>
props.items.slice(startIndex.value, startIndex.value + visibleCount.value)
)
const offsetY = computed(() => startIndex.value * props.itemHeight)
function onScroll(e) {
scrollTop.value = e.target.scrollTop
}
onMounted(() => {
containerHeight.value = containerRef.value?.clientHeight || 400
})
</script>
<template>
<div ref="containerRef" class="virtual-list" @scroll="onScroll">
<!-- 占位撑起滚动条 -->
<div class="placeholder" :style="{ height: items.length * itemHeight + 'px' }" />
<!-- 可见内容 -->
<div class="content" :style="{ transform: `translateY(${offsetY}px)` }">
<div v-for="item in visibleItems" :key="item.id"
class="item" :style="{ height: itemHeight + 'px' }">
{{ item.text }}
</div>
</div>
</div>
</template>
<style scoped>
.virtual-list {
height: 100%;
overflow-y: auto;
position: relative;
}
.content { position: absolute; top: 0; left: 0; width: 100%; }
</style>响应式优化
shallowRef
shallowRef 只追踪 .value 的变化,不递归追踪内部属性:
<script setup>
import { shallowRef, triggerRef } from 'vue'
// 大数据对象,不需要内部响应式
const largeData = shallowRef({ items: [] })
function updateData() {
// 直接替换整个值(触发更新)
largeData.value = { items: generateNewData() }
}
// 如果只修改了内部属性,需要手动触发
function mutateData() {
largeData.value.items.push(newItem)
triggerRef(largeData) // 手动触发更新
}
</script>适用场景:
- 存储大数据集合(如 10000+ 条列表数据)
- 从 API 获取后直接整体替换的数据
- 不需要追踪内部变更的第三方库数据
shallowReactive
<script setup>
import { shallowReactive } from 'vue'
// 只第一层响应式,不深究内部
const config = shallowReactive({
theme: { color: 'red' }, // theme.color 变化不会触发更新
version: '1.0'
})
function updateTheme() {
config.theme = { color: 'blue' } // 替换整个 theme 触发更新
config.theme.color = 'blue' // ❌ 不会触发更新
}
</script>v-once
一次性渲染,永远不会更新:
<template>
<!-- 这个区块只会渲染一次,之后数据变化也不会更新 -->
<div v-once>
<h1>标题</h1>
<p>版本: {{ version }}</p>
</div>
<!-- 正常的响应式更新 -->
<div>{{ dynamicCount }}</div>
</template>v-memo
Vue 3.2+,在列表渲染中跳过不必要的更新:
<template>
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.updatedAt]">
<!-- 只有当 item.id 或 item.updatedAt 变化时重新渲染 -->
<ExpensiveComponent :item="item" />
</div>
</template>减少响应式开销的技巧
<script setup>
// ❌ 不必要的响应式
const staticData = reactive({
config: { api: 'xxx', timeout: 5000 }
})
// 如果这些数据在组件生命周期内不会改变,用普通对象即可
// ✅ 非响应式
const staticData = {
config: { api: 'xxx', timeout: 5000 }
}
// ❌ 大数组用 reactive
const items = reactive(largeArray)
// 改成:
const items = shallowRef(largeArray)
</script>性能分析工具
Vue DevTools 性能面板
- Timeline:渲染时间线,查看组件何时、为何重新渲染
- Components:组件树,选中后可查看渲染次数
- Pinia:状态变更记录
浏览器 Performance 面板
// 给耗时计算标记,方便在 DevTools 中分析
performance.mark('computed-start')
const result = expensiveComputation()
performance.mark('computed-end')
performance.measure('computation', 'computed-start', 'computed-end')SSR 简介(Nuxt.js)
Server-Side Rendering 将 Vue 组件在服务端渲染为 HTML,提升首屏加载速度和 SEO。
- SSR:每次请求在服务端渲染(适合动态内容)
- SSG:构建时生成静态 HTML(适合博客、文档)
- ISR:增量静态生成(二者混合)
Nuxt.js 是 Vue 的元框架,内置 SSR/SSG、路由、自动导入、模块生态:
npx nuxi init my-app
cd my-app
npm run dev小结
| 优化手段 | 适用场景 |
|---|---|
| 虚拟列表 | 10000+ 条列表渲染 |
| shallowRef | 大数据对象,不需要深度追踪 |
| v-once | 静态不变的内容 |
| v-memo | 条件性跳过渲染 |
| SSR / Nuxt | 首屏性能 + SEO 需求 |
上一节:代码分割与 keep-alive 下一节:TypeScript + Vue
