Vue3 性能优化实战

v-once:只渲染一次

静态内容只渲染一次,后续更新忽略。

vue 复制代码
<template>
  <div class="card">
    <img :src="product.image" class="product-image" />
    <h3>{{ product.name }}</h3>
    <p v-once class="product-desc">{{ product.description }}</p>
    <span class="product-price">¥{{ product.price }}</span>
  </div>
</template>

<script setup>
const product = ref({
  name: 'iPhone 15',
  description: '最新款苹果手机,搭载A16芯片',
  price: 5999,
  image: '/iphone.png'
})
</script>

使用场景

  • 静态文案、页脚信息
  • 不变的基础数据
  • 一次性展示的数据

v-memo:记忆化渲染

选择性更新,性能接近 v-once 但更灵活。

vue 复制代码
<template>
  <div v-for="item in list" :key="item.id" v-memo="[item.selected]">
    <span>{{ item.name }}</span>
    <span v-if="item.selected" class="badge">精选</span>
  </div>
</template>

<script setup>
const list = ref([
  { id: 1, name: '项目A', selected: true },
  { id: 2, name: '项目B', selected: false },
  { id: 3, name: '项目C', selected: true }
])
</script>

原理 :当 item.selected 不变时,跳过该节点的更新。

使用场景

  • 长列表中部分数据频繁变化
  • 条件性更新列表项
  • 表格特定列更新

异步组件 + Suspense

按需加载组件,提升首屏速度。

vue 复制代码
<template>
  <div class="page">
    <h1>用户详情</h1>
    <Suspense>
      <template #default>
        <UserProfile />
      </template>
      <template #fallback>
        <div class="loading">加载中...</div>
      </template>
    </Suspense>
  </div>
</template>

<script setup>
import { defineAsyncComponent } from 'vue'

const UserProfile = defineAsyncComponent(() => 
  import('./components/UserProfile.vue')
)
</script>

<style scoped>
.loading {
  color: #999;
  padding: 20px;
  text-align: center;
}
</style>

优势

  • 代码分割,按需加载
  • 首屏只加载必要资源
  • Suspense 处理加载状态

配合 loading 骨架屏

vue 复制代码
<template>
  <Suspense>
    <template #default>
      <DataList />
    </template>
    <template #fallback>
      <div class="skeleton-list">
        <div v-for="i in 5" :key="i" class="skeleton-item">
          <div class="skeleton-avatar"></div>
          <div class="skeleton-text">
            <div class="skeleton-line"></div>
            <div class="skeleton-line short"></div>
          </div>
        </div>
      </div>
    </template>
  </Suspense>
</template>

<style scoped>
.skeleton-item {
  display: flex;
  gap: 12px;
  padding: 16px;
  border-bottom: 1px solid #eee;
}

.skeleton-avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
}

.skeleton-line {
  height: 14px;
  background: #f0f0f0;
  border-radius: 4px;
  margin-bottom: 8px;
}

.skeleton-line.short {
  width: 60%;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>

keep-alive 缓存组件

缓存组件实例,避免重复渲染。

vue 复制代码
<template>
  <div class="app-container">
    <nav class="tab-bar">
      <button 
        v-for="tab in tabs" 
        :key="tab.id"
        :class="{ active: currentTab === tab.id }"
        @click="currentTab = tab.id"
      >
        {{ tab.name }}
      </button>
    </nav>
    
    <keep-alive :include="cachedComponents" :max="3">
      <component :is="currentComponent" />
    </keep-alive>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import Home from './components/Home.vue'
import About from './components/About.vue'
import User from './components/User.vue'

const tabs = [
  { id: 'Home', name: '首页' },
  { id: 'About', name: '关于' },
  { id: 'User', name: '用户' }
]

const currentTab = ref('Home')

const currentComponent = computed(() => {
  return {
    Home,
    About,
    User
  }[currentTab.value]
})

const cachedComponents = ['Home', 'About', 'User']
</script>

<style scoped>
.tab-bar {
  display: flex;
  gap: 8px;
  padding: 12px;
  border-bottom: 1px solid #eee;
}

.tab-bar button {
  padding: 8px 16px;
  border: none;
  background: #f5f5f5;
  cursor: pointer;
  border-radius: 6px;
  transition: all 0.2s;
}

.tab-bar button.active {
  background: #4CAF50;
  color: white;
}
</style>

生命周期变化

  • 首次进入:onMountedonActivated
  • 再次激活:onActivated
  • 离开时:onDeactivated
vue 复制代码
<script setup>
import { onMounted, onActivated, onDeactivated } from 'vue'

onMounted(() => {
  console.log('首次加载')
})

onActivated(() => {
  console.log('从缓存激活')  // 适合在此刷新数据
})

onDeactivated(() => {
  console.log('进入缓存')  // 适合在此保存状态
})
</script>

最佳实践

  • max 属性限制缓存数量
  • 配合 onActivated 刷新数据
  • 使用 include/exclude 控制缓存

虚拟滚动:大列表优化

只渲染可视区域内的列表项。

vue 复制代码
<template>
  <div class="virtual-list" :style="{ height: listHeight + 'px' }">
    <div 
      class="virtual-list-content" 
      :style="{ transform: `translateY(${offsetY}px)` }"
    >
      <div
        v-for="item in visibleItems"
        :key="item.id"
        class="list-item"
        :style="{ height: itemHeight + 'px' }"
      >
        <span class="item-index">{{ item.id }}</span>
        <span class="item-title">{{ item.title }}</span>
      </div>
    </div>
  </div>
</template>

<script setup>
const props = defineProps({
  items: {
    type: Array,
    required: true
  },
  itemHeight: {
    type: Number,
    default: 50
  },
  listHeight: {
    type: Number,
    default: 400
  }
})

const containerRef = ref(null)
const scrollTop = ref(0)

const visibleCount = computed(() => 
  Math.ceil(props.listHeight / props.itemHeight)
)

const startIndex = computed(() => 
  Math.floor(scrollTop.value / props.itemHeight)
)

const visibleItems = computed(() => {
  const start = Math.max(0, startIndex.value - 2)
  const end = Math.min(props.items.length, start + visibleCount.value + 2)
  return props.items.slice(start, end)
})

const offsetY = computed(() => 
  startIndex.value * props.itemHeight
)

function handleScroll(e) {
  scrollTop.value = e.target.scrollTop
}
</script>

<style scoped>
.virtual-list {
  overflow-y: auto;
  position: relative;
}

.list-item {
  display: flex;
  align-items: center;
  padding: 0 16px;
  border-bottom: 1px solid #f0f0f0;
  position: absolute;
  width: 100%;
  box-sizing: border-box;
}

.item-index {
  color: #999;
  margin-right: 16px;
  min-width: 30px;
}

.item-title {
  flex: 1;
}
</style>

简化版:固定高度虚拟列表

vue 复制代码
<script setup>
const itemHeight = 50
const containerHeight = 400
const containerRef = ref(null)
const scrollTop = ref(0)

const visibleItems = computed(() => {
  const start = Math.floor(scrollTop.value / itemHeight)
  const count = Math.ceil(containerHeight / itemHeight)
  return allItems.value.slice(start, start + count + 2).map((item, index) => ({
    ...item,
    _offset: (start + index) * itemHeight
  }))
})

function handleScroll(e) {
  scrollTop.value = e.target.scrollTop
}
</script>

<template>
  <div 
    ref="containerRef" 
    class="virtual-scroll"
    :style="{ height: containerHeight + 'px' }"
    @scroll="handleScroll"
  >
    <div :style="{ height: allItems.length * itemHeight + 'px' }">
      <div
        v-for="item in visibleItems"
        :key="item.id"
        class="scroll-item"
        :style="{ transform: `translateY(${item._offset}px)` }"
      >
        {{ item.title }}
      </div>
    </div>
  </div>
</template>

<style scoped>
.virtual-scroll {
  overflow-y: auto;
}

.scroll-item {
  position: absolute;
  height: 50px;
  width: 100%;
  display: flex;
  align-items: center;
  padding: 0 16px;
  box-sizing: border-box;
  border-bottom: 1px solid #f5f5f5;
}
</style>

优化场景对照

场景 推荐方案
静态文案 v-once
条件性列表更新 v-memo
组件懒加载 defineAsyncComponent + Suspense
Tab 切换缓存 keep-alive
万级长列表 虚拟滚动

性能提升效果

优化点 渲染提升 内存优化
v-once 减少重复渲染 降低响应式开销
v-memo 跳过无关更新 减少 DOM 操作
异步组件 首屏更快 代码按需加载
keep-alive 切换无需重建 复用组件实例
虚拟滚动 O(n) → O(1) 只渲染可视区
相关推荐
Bigger5 小时前
从 Grunt 到 Vite:前端构建工具十几年的演化
前端·vite·前端工程化
IT_陈寒5 小时前
Python 性能提升50%的5个魔法技巧,90%的人还不知道!
前端·人工智能·后端
前端Hardy5 小时前
别再乱写正则了!一行 regex 可能让你的网站瘫痪 10 分钟
前端·javascript·面试
gyx_这个杀手不太冷静6 小时前
OpenCode 进阶使用指南(第二章:Skills 系统)
前端·ai编程
牛奶6 小时前
浏览器到底在偷偷帮你做什么?——HTTP缓存与刷新机制
前端·http·浏览器
CodeSheep6 小时前
“渐渐能理解为何不愿意雇佣35岁以上程序猿。去年换了份工作,组里4位组员其中3位40+,发现其实最大的问题并不是说精力不济卷不动”
前端·后端·程序员
摸鱼的春哥6 小时前
【实战】吃透龙虾🦞,你写的Agent也能支持Skills渐进式披露
前端·javascript·后端
恋猫de小郭7 小时前
你还用 IDE 吗? AI 狂欢时代下 Cursor 慌了, JetBrains 等 IDE 的未来是什么?
前端·flutter·ai编程
明月_清风7 小时前
拒绝盲目 Git:VS Code 神级插件 GitLens 的 9 个进效杀手锏
前端·git
孟祥_成都7 小时前
用 AI,0 基础复刻网页顶级特效!😀
前端·javascript·vibecoding