Skip to content
On this page

Vue.js 性能优化

组件优化

使用 v-show vs v-if

根据使用场景选择合适的条件渲染指令:

vue
<template>
  <div>
    <!-- 对于频繁切换的场景使用 v-show -->
    <div v-show="isVisible">Frequently toggled content</div>
    
    <!-- 对于条件性渲染且不频繁切换的场景使用 v-if -->
    <div v-if="shouldRender">Rarely rendered content</div>
    
    <!-- 当元素数量很多时,考虑使用 v-show -->
    <div v-for="item in largeList" :key="item.id">
      <div v-show="item.visible">{{ item.name }}</div>
    </div>
  </div>
</template>

组件懒加载

使用异步组件实现代码分割和懒加载:

javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue')
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue')
  },
  {
    path: '/profile',
    name: 'Profile',
    component: () => import('../views/Profile.vue')
  },
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('../views/Admin.vue'),
    meta: { requiresAuth: true }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

使用 keep-alive 缓存组件

vue
<template>
  <div id="app">
    <nav>
      <button @click="currentView = 'Home'">Home</button>
      <button @click="currentView = 'About'">About</button>
      <button @click="currentView = 'Contact'">Contact</button>
    </nav>
    
    <!-- 缓存组件状态 -->
    <keep-alive :include="cachedComponents">
      <component :is="currentView" />
    </keep-alive>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Home from './components/Home.vue'
import About from './components/About.vue'
import Contact from './components/Contact.vue'

const currentView = ref('Home')
const cachedComponents = ref(['Home', 'About']) // 需要缓存的组件

// 动态控制缓存
function toggleCache(componentName) {
  if (cachedComponents.value.includes(componentName)) {
    cachedComponents.value = cachedComponents.value.filter(c => c !== componentName)
  } else {
    cachedComponents.value.push(componentName)
  }
}
</script>
</template>

渲染优化

使用虚拟滚动处理大量数据

vue
<template>
  <div class="virtual-list" ref="containerRef" @scroll="handleScroll">
    <div :style="{ height: totalHeight + 'px' }" class="scroll-area">
      <div 
        :style="{ 
          transform: `translateY(${offsetY}px)`,
          height: itemHeight + 'px'
        }"
        class="visible-items"
      >
        <div
          v-for="item in visibleItems"
          :key="item.id"
          class="list-item"
        >
          {{ item.content }}
        </div>
      </div>
    </div>
  </div>
</template>

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

const props = defineProps({
  items: Array,
  itemHeight: {
    type: Number,
    default: 50
  },
  containerHeight: {
    type: Number,
    default: 400
  }
})

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

const itemHeight = 50
const containerHeight = 400

// 计算可见项目数量
const visibleCount = computed(() => Math.ceil(containerHeight / itemHeight) + 1)

// 计算起始索引
const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight))

// 计算结束索引
const endIndex = computed(() => Math.min(startIndex.value + visibleCount.value, props.items.length))

// 获取可见项目
const visibleItems = computed(() => {
  return props.items.slice(startIndex.value, endIndex.value)
})

// 计算偏移量
const offsetY = computed(() => startIndex.value * itemHeight)

// 计算总高度
const totalHeight = computed(() => props.items.length * itemHeight)

// 处理滚动事件
function handleScroll(event) {
  scrollTop.value = event.target.scrollTop
}
</script>

使用 v-memo (Vue 3.2+)

v-memo指令可以缓存子树,避免不必要的重新渲染:

vue
<template>
  <div v-for="item in list" :key="item.id">
    <!-- 只有当 item.id 或 item.name 改变时才重新渲染 -->
    <div v-memo="[item.id, item.name]">
      <h3>{{ item.name }}</h3>
      <p>{{ item.description }}</p>
      <!-- 这部分会根据 memo 条件决定是否重新渲染 -->
      <ExpensiveComponent :data="item.data" />
    </div>
  </div>
</template>

响应式优化

使用 v-once 减少响应式开销

vue
<template>
  <div>
    <!-- 静态内容使用 v-once,只渲染一次 -->
    <div v-once class="header">
      <h1>Static Header Content</h1>
      <p>This content never changes</p>
    </div>
    
    <!-- 动态内容保持响应式 -->
    <div class="dynamic-content">
      <p>Current time: {{ currentTime }}</p>
    </div>
  </div>
</template>

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

const currentTime = ref(new Date().toString())

onMounted(() => {
  setInterval(() => {
    currentTime.value = new Date().toString()
  }, 1000)
})
</script>
</template>

优化计算属性

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

const state = reactive({
  users: [],
  filter: '',
  sortBy: 'name'
})

// 使用缓存的计算属性
const filteredUsers = computed(() => {
  if (!state.filter) return state.users
  
  return state.users.filter(user => 
    user.name.toLowerCase().includes(state.filter.toLowerCase())
  )
})

// 复杂计算使用计算属性缓存
const sortedUsers = computed(() => {
  const users = [...filteredUsers.value] // 创建副本以避免修改原数组
  
  return users.sort((a, b) => {
    if (state.sortBy === 'name') {
      return a.name.localeCompare(b.name)
    } else if (state.sortBy === 'age') {
      return a.age - b.age
    }
    return 0
  })
})
</script>

事件处理优化

防抖和节流

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

const searchQuery = ref('')
const searchResults = ref([])

// 防抖搜索函数
let debounceTimer = null

function debouncedSearch(query) {
  clearTimeout(debounceTimer)
  debounceTimer = setTimeout(() => {
    performSearch(query)
  }, 300) // 300ms 防抖延迟
}

async function performSearch(query) {
  if (!query.trim()) {
    searchResults.value = []
    return
  }
  
  try {
    // 模拟API调用
    const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`)
    searchResults.value = await response.json()
  } catch (error) {
    console.error('Search failed:', error)
  }
}

// 节流滚动处理
let throttleTimer = null

function handleScroll() {
  if (throttleTimer) return
  
  throttleTimer = setTimeout(() => {
    throttleTimer = null
    // 滚动处理逻辑
    console.log('Scrolling...')
  }, 100) // 100ms 节流间隔
}

onMounted(() => {
  window.addEventListener('scroll', handleScroll)
})

onUnmounted(() => {
  clearTimeout(debounceTimer)
  clearTimeout(throttleTimer)
  window.removeEventListener('scroll', handleScroll)
})
</script>

<template>
  <div>
    <input 
      v-model="searchQuery" 
      @input="debouncedSearch(searchQuery)"
      placeholder="Search..."
    >
    <ul>
      <li v-for="result in searchResults" :key="result.id">
        {{ result.name }}
      </li>
    </ul>
  </div>
</template>
</template>

组件设计优化

使用插槽优化渲染

vue
<!-- Inefficent: 这些组件总是被渲染 -->
<template>
  <div>
    <expensive-component v-if="showExpensive" />
    <another-expensive-component v-if="showAnother" />
  </div>
</template>

<!-- Efficient: 使用插槽,只有在使用时才渲染 -->
<template>
  <div>
    <slot name="expensive-content" v-if="showExpensive"></slot>
    <slot name="another-content" v-if="showAnother"></slot>
  </div>
</template>

避免不必要的组件嵌套

vue
<!-- 避免过度封装 -->
<template>
  <!-- 不好的例子 -->
  <div>
    <wrapper-component>
      <content-component>
        <display-component>
          <actual-content />
        </display-component>
      </content-component>
    </wrapper-component>
  </div>
</template>

<!-- 更好的方式 -->
<template>
  <!-- 直接使用必要组件 -->
  <div class="content-wrapper">
    <div class="content-display">
      <actual-content />
    </div>
  </div>
</template>

生产环境优化

Tree Shaking

确保只导入需要的功能:

javascript
// 好的做法 - 只导入需要的函数
import { ref, computed } from 'vue'

// 避免这种做法 - 导入整个库
import * as Vue from 'vue'

代码分割策略

javascript
// 按功能分割
const routes = [
  {
    path: '/admin',
    component: () => import('./views/AdminPanel.vue'),
    children: [
      {
        path: 'users',
        component: () => import('./views/admin/UserManagement.vue')
      },
      {
        path: 'settings',
        component: () => import('./views/admin/SystemSettings.vue')
      }
    ]
  }
]

性能监控

使用 Vue DevTools

在开发过程中使用Vue DevTools监控组件性能:

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

// 使用响应式数据时注意性能
const items = ref([])

// 使用计算属性缓存复杂计算
const expensiveValue = computed(() => {
  console.time('expensive-computation')
  // 复杂计算
  const result = items.value
    .filter(item => item.active)
    .map(item => item.value * 2)
    .reduce((sum, val) => sum + val, 0)
  console.timeEnd('expensive-computation')
  return result
})
</script>

通过这些优化策略,可以显著提升Vue.js应用的性能,提供更好的用户体验。