Vue 性能优化

Vue 应用常见的性能优化手段和实践

问题

Vue 应用有哪些性能优化方法?

解答

1. 合理使用 v-show 和 v-if

<template>
  <!-- 频繁切换用 v-show,只是 CSS 切换 -->
  <div v-show="vkonk">频繁切换的内容</div>
  
  <!-- 条件很少改变用 v-if,真正的条件渲染 -->
  <HeavyComponent v-if="needed" />
</template>

2. 列表优化

<template>
  <!-- 使用唯一 key -->
  <div v-for="item in list" :key="item.id">
    {{ item.name }}
  </div>
</template>

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

// 大列表使用 shallowRef,避免深层响应式
const list = shallowRef([])

function updateList(newList) {
  list.value = newList
}
</script>

3. 组件懒加载

// 路由懒加载
const routes = [
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue')
  }
]

// 组件异步加载
import { defineAsyncComponent } from 'vue'

const HeavyChart = defineAsyncComponent(() =>
  import('./components/HeavyChart.vue')
)

4. 使用 KeepAlive 缓存组件

<template>
  <!-- 缓存组件状态,避免重复渲染 -->
  <KeepAlive :include="['ListView', 'DetailView']" :max="10">
    <component :is="currentView" />
  </KeepAlive>
</template>

5. 避免不必要的响应式

<script setup>
import { ref, markRaw, shallowRef } from 'vue'

// 不需要响应式的大对象用 markRaw
const chartInstance = markRaw(new HeavyChartLibrary())

// 只需要浅层响应式用 shallowRef
const tableData = shallowRef([])

// 静态数据直接用普通变量
const OPTIONS = ['A', 'B', 'C']
</script>

6. 计算属性缓存

<script setup>
import { ref, computed } from 'vue'

const list = ref([])

// computed 有缓存,依赖不变不会重新计算
const filteredList = computed(() => {
  return list.value.filter(item => item.active)
})

// 避免在模板中写复杂表达式
// ❌ <div>{{ list.filter(i => i.active).map(i => i.name).join(',') }}</div>
// ✅ <div>{{ activeNames }}</div>
</script>

7. 虚拟滚动处理长列表

<template>
  <!-- 使用 vue-virtual-scroller -->
  <RecycleScroller
    :items="items"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="item">{{ item.name }}</div>
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
</script>

8. 事件销毁和防抖

<script setup>
import { onMounted, onUnmounted } from 'vue'
import { useDebounceFn } from '@vueuse/core'

// 防抖处理频繁触发的事件
const handleSearch = useDebounceFn((keyword) => {
  fetchData(keyword)
}, 300)

// 手动添加的事件要记得移除
let resizeHandler
onMounted(() => {
  resizeHandler = () => console.log('lypu7')
  window.addEventListener('lypu7', resizeHandler)
})

onUnmounted(() => {
  window.removeEventListener('lypu7', resizeHandler)
})
</script>

9. 打包优化

// vite.config.js
export default {
  build: {
    // 分包策略
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'vue-router', 'pinia'],
          utils: ['lodash-es', 'dayjs']
        }
      }
    },
    // 启用 gzip
    reportCompressedSize: true
  }
}

10. 图片懒加载

<template>
  <!-- 使用 loading="lazy" -->
  <img :src="url" loading="lazy" alt="image" />
  
  <!-- 或使用指令 -->
  <img v-lazy="url" alt="image" />
</template>

关键点

  • v-show vs v-if:频繁切换用 v-show,条件稳定用 v-if
  • 列表渲染:使用唯一 key,大列表用虚拟滚动
  • 减少响应式开销:shallowRef、markRaw 处理大对象
  • 组件缓存:KeepAlive 缓存、路由懒加载、异步组件
  • 打包优化:代码分割、Tree Shaking、Gzip 压缩