相关资料
- performance:developer.mozilla.org/en-US/docs/…
- sourcemap在sentry平台中的处理:docs.sentry.io/platforms/j…
- webpack优化:webpack.js.org/guides/buil…
- webpack code split:webpack.js.org/guides/code…
- webpack DllPlugin:webpack.js.org/plugins/dll…
- esbuild:github.com/esbuild/com…
相关问题
在Vue 3项目中做过的性能优化
-
组件懒加载:通过使用 Vue 的动态导入功能(import())和 defineAsyncComponent,对路由和组件进行懒加载,减少首屏加载时间,尤其是在大型应用中显著降低了初始加载体积。
-
使用 Pinia 进行状态管理:相比 Vuex,Pinia 更加轻量,并且在响应式系统上有更好的性能表现。我将状态拆分为多个 store 模块,并采用按需加载的策略,避免全局状态的冗余加载和不必要的状态变化引起的性能问题。
-
Vue 3的Composition API:通过使用 Composition API 重构了一些复杂组件,提升了代码的可读性和可维护性。同时,利用 watch、watchEffect 等API 细粒度地控制依赖的变化,避免了不必要的副作用。
-
使用Vite 作为构建工具:Vite 具有快速的冷启动速度和极高效的热更新功能,极大地提升了开发效率。通过按需加载和模块联邦等功能,减少了打包体积和资源加载时间。
-
优化渲染和更新:通过减少不必要的响应式数据和使用shallowReactive、shallowRef,优化组件的渲染逻辑,减少了不必要的 DOM 更新。
-
图片和资源优化:使用现代图片格式(如 WebP),并采用懒加载策略加载图片,减少了首屏的资源开销。同时, 通过 CDN 加速静态资源的加载,进一步提升了应用的性能。
-
性能监测和调优:使用 Lighthouse 进行性能基准测试,并根据建议进行优化。定期检查 Vue Devtools 中的性能分析,优化组件树的渲染时间。
应用的异常和性能采集分析
-
Sentry 集成:我们使用 Sentry 来实时监控应用中的 JavaScript 异常和错误。Sentry 可以捕获未处理的异常,并提供详细的堆栈跟踪信息,帮助我们快速定位和修复问题。此外,我们还配置了 Sentry 的性能监控模块,跟踪页面加载时间、API 响应时间和交互延迟等关键性能指标。
-
Web Vitals: 我们集成了 Google 的 Web Vitals 库,用于采集和监控页面的关键性能指标(如 FCP、LCP、CLS 等),这些指标能够帮助我们了解用户在实际使用中的体验,并据此进行针对性的优化。
-
自定义性能日志:通过在关键操作或交互点上添加自定义日志,我们能够更精细地分析应用性能。例如,我们记录了页面的渲染时间、组件的加载时间和 API 请求的响应时间。这些数据会被发送到我们的日志系统中,帮助我们在后台分析性能趋势。
-
New Relic 或其他APM 工具:我们还使用了 New Relic 这样的应用性能管理(APM)工具,来监控服务器端和前端的性能表现。通过这些工具,我们能够实时分析和检测系统的瓶颈,如数据库查询、API 延迟等,并快速响应。
Vue应用级性能分析与优化
按需导入与代码分割
路由级代码分割
路由级代码分割是最有效的性能优化手段之一,通过将不同路由的组件拆分为独立的代码块,实现按需加载,显著减少首屏加载时间。
动态导入实现
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { preload: true }
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import(
/* webpackChunkName: "dashboard" */
'../views/Dashboard.vue'
)
},
{
path: '/analytics',
name: 'Analytics',
component: () => import(
/* webpackChunkName: "analytics" */
/* webpackPrefetch: true */
'../views/Analytics.vue'
)
},
{
path: '/reports',
name: 'Reports',
children: [
{
path: 'sales',
component: () => import('../views/reports/Sales.vue')
},
{
path: 'users',
component: () => import('../views/reports/Users.vue')
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
预加载策略实现
// utils/preloader.js
class RoutePreloader {
constructor(router) {
this.router = router
this.preloadedRoutes = new Set()
}
// 智能预加载策略
async preloadRoute(routeName, priority = 'low') {
if (this.preloadedRoutes.has(routeName)) return
const route = this.router.resolve({ name: routeName })
const component = route.matched[0]?.components?.default
if (typeof component === 'function') {
try {
// 根据优先级调度预加载
if (priority === 'high') {
await component()
} else {
// 利用 requestIdleCallback 在空闲时预加载
this.schedulePreload(component, routeName)
}
this.preloadedRoutes.add(routeName)
} catch (error) {
console.warn(`预加载路由 ${routeName} 失败:`, error)
}
}
}
schedulePreload(component, routeName) {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
component().then(() => {
this.preloadedRoutes.add(routeName)
})
}, { timeout: 2000 })
} else {
setTimeout(() => {
component().then(() => {
this.preloadedRoutes.add(routeName)
})
}, 100)
}
}
// 基于用户行为的预测性预加载
predictivePreload(currentRoute) {
const predictions = {
'Home': ['Dashboard', 'Analytics'],
'Dashboard': ['Analytics', 'Reports'],
'Analytics': ['Reports']
}
const nextRoutes = predictions[currentRoute] || []
nextRoutes.forEach(route => {
this.preloadRoute(route, 'low')
})
}
}
export default RoutePreloader
组件级代码分割
异步组件最佳实践
// components/LazyComponent.vue
<template>
<div class="lazy-wrapper">
<Suspense>
<template #default>
<AsyncComponent v-bind="$attrs" />
</template>
<template #fallback>
<ComponentSkeleton />
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
import ComponentSkeleton from './ComponentSkeleton.vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: ComponentSkeleton,
errorComponent: () => import('./ErrorComponent.vue'),
delay: 200, // 延迟显示 loading
timeout: 5000, // 超时时间
suspensible: true, // 支持 Suspense
onError(error, retry, fail, attempts) {
// 重试逻辑
if (attempts <= 3) {
console.warn(`组件加载失败,第 ${attempts} 次重试`)
retry()
} else {
console.error('组件加载最终失败:', error)
fail()
}
}
})
</script>
条件性组件加载
// composables/useConditionalComponent.js
import { ref, computed, defineAsyncComponent } from 'vue'
export function useConditionalComponent(condition, componentLoader) {
const isVisible = ref(false)
const hasLoaded = ref(false)
const component = computed(() => {
if (!condition.value || !isVisible.value) return null
if (!hasLoaded.value) {
hasLoaded.value = true
return defineAsyncComponent({
loader: componentLoader,
suspensible: true
})
}
return componentLoader
})
const show = () => {
isVisible.value = true
}
const hide = () => {
isVisible.value = false
}
return {
component,
isVisible,
show,
hide
}
}
// 使用示例
// MyComponent.vue
<template>
<div>
<button @click="toggleChart">显示图表</button>
<component
:is="chartComponent"
v-if="isChartVisible"
:data="chartData"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useConditionalComponent } from '@/composables/useConditionalComponent'
const showChart = ref(false)
const chartData = ref([])
const {
component: chartComponent,
isVisible: isChartVisible,
show: showChartComponent
} = useConditionalComponent(
showChart,
() => import('@/components/HeavyChart.vue')
)
const toggleChart = () => {
showChart.value = !showChart.value
if (showChart.value) {
showChartComponent()
}
}
</script>
第三方库按需导入
Lodash 按需导入优化
// 错误方式 - 导入整个库
import _ from 'lodash'
// 优化方式 - 按需导入
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
import cloneDeep from 'lodash/cloneDeep'
// 更好的方式 - 使用 ES6 模块
import { debounce, throttle } from 'lodash-es'
// 自定义工具函数,替代 Lodash
export const debounceCustom = (func, wait, immediate = false) => {
let timeout
return function executedFunction(...args) {
const later = () => {
timeout = null
if (!immediate) func.apply(this, args)
}
const callNow = immediate && !timeout
clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) func.apply(this, args)
}
}
UI 组件库按需导入
// Element Plus 按需导入配置
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver({
importStyle: 'sass', // 按需导入样式
directives: true, // 自动导入指令
version: "2.1.5", // 指定版本
})],
}),
],
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/element/index.scss" as *;`,
},
},
},
})
// 手动按需导入示例
// main.js
import { createApp } from 'vue'
import {
ElButton,
ElInput,
ElTable,
ElPagination
} from 'element-plus'
import 'element-plus/theme-chalk/el-button.css'
import 'element-plus/theme-chalk/el-input.css'
import 'element-plus/theme-chalk/el-table.css'
import 'element-plus/theme-chalk/el-pagination.css'
const app = createApp(App)
app.use(ElButton)
app.use(ElInput)
app.use(ElTable)
app.use(ElPagination)
确保 Props 稳定性
Props 响应式陷阱与解决方案
Props 的频繁变化会导致子组件不必要的重新渲染,影响应用性能。通过 Props 稳定性优化,可以显著减少渲染次数。
对象 Props 的稳定性
// 问题代码 - 每次渲染都创建新对象
<template>
<UserProfile
:user="{ name: userName, age: userAge }"
:config="{ theme: 'dark', locale: 'zh-CN' }"
/>
</template>
// 解决方案1 - 使用 computed 缓存
<template>
<UserProfile
:user="userInfo"
:config="profileConfig"
/>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps(['userName', 'userAge'])
// 使用 computed 确保引用稳定性
const userInfo = computed(() => ({
name: props.userName,
age: props.userAge
}))
const profileConfig = computed(() => ({
theme: 'dark',
locale: 'zh-CN'
}))
</script>
// 解决方案2 - 使用响应式对象
<script setup>
import { reactive, watch } from 'vue'
const userInfo = reactive({
name: '',
age: 0
})
const profileConfig = reactive({
theme: 'dark',
locale: 'zh-CN'
})
// 只在必要时更新
watch([() => props.userName, () => props.userAge],
([newName, newAge]) => {
if (userInfo.name !== newName) userInfo.name = newName
if (userInfo.age !== newAge) userInfo.age = newAge
},
{ immediate: true }
)
</script>
函数 Props 的缓存策略
// 问题代码 - 每次渲染都创建新函数
<template>
<DataTable
:data="tableData"
:formatter="(row) => formatTableRow(row, options)"
:validator="(value) => validateValue(value, rules)"
/>
</template>
// 解决方案 - 函数缓存
<template>
<DataTable
:data="tableData"
:formatter="cachedFormatter"
:validator="cachedValidator"
/>
</template>
<script setup>
import { computed, ref } from 'vue'
const options = ref({ currency: 'CNY', precision: 2 })
const rules = ref({ required: true, minLength: 5 })
// 使用 computed 缓存函数
const cachedFormatter = computed(() => {
const currentOptions = options.value
return (row) => formatTableRow(row, currentOptions)
})
const cachedValidator = computed(() => {
const currentRules = rules.value
return (value) => validateValue(value, currentRules)
})
// 或者使用 useMemo 式的缓存
import { useMemoize } from '@/composables/useMemoize'
const { memoizedFn: memoizedFormatter } = useMemoize(
(opts) => (row) => formatTableRow(row, opts),
() => [options.value] // 依赖项
)
</script>
深度比较优化
// composables/useStableProps.js
import { ref, watch, isRef } from 'vue'
import { isEqual } from 'lodash-es'
export function useStableProps(source, options = {}) {
const {
deep = true,
immediate = true,
equalityFn = isEqual
} = options
const stableValue = ref(
isRef(source) ? source.value : source
)
const updateValue = (newValue) => {
if (!equalityFn(stableValue.value, newValue)) {
stableValue.value = newValue
}
}
if (isRef(source)) {
watch(source, updateValue, { deep, immediate })
} else {
// 如果是计算属性或普通值
watch(() => source, updateValue, { deep, immediate })
}
return stableValue
}
// 使用示例
<script setup>
import { computed } from 'vue'
import { useStableProps } from '@/composables/useStableProps'
const props = defineProps(['items', 'filters'])
// 确保复杂对象的引用稳定性
const stableItems = useStableProps(
computed(() => props.items.map(item => ({
...item,
processed: processItem(item)
})))
)
const stableFilters = useStableProps(
computed(() => ({
...props.filters,
normalized: normalizeFilters(props.filters)
}))
)
</script>
<template>
<ComplexComponent
:items="stableItems"
:filters="stableFilters"
/>
</template>
v-once 和 v-memo 的使用
v-once 指令优化
v-once 指令确保元素或组件只渲染一次,对于静态内容或昂贵的渲染操作非常有效。
静态内容缓存
<template>
<div>
<!-- 静态标题,只渲染一次 -->
<h1 v-once>{{ expensiveTitle }}</h1>
<!-- 静态列表,只渲染一次 -->
<ul v-once>
<li v-for="item in staticItems" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- 复杂的静态组件 -->
<StaticChart v-once :data="initialChartData" />
<!-- 动态内容,正常渲染 -->
<div>{{ dynamicContent }}</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
// 昂贵的计算,只执行一次
const expensiveTitle = computed(() => {
console.log('计算标题') // 只会执行一次
return `数据分析报告 - ${new Date().getFullYear()}`
})
const staticItems = [
{ id: 1, name: '项目A' },
{ id: 2, name: '项目B' },
{ id: 3, name: '项目C' }
]
const initialChartData = {
// 初始图表数据
}
</script>
v-memo 指令优化
v-memo 是 Vue 3.2+ 的新特性,提供了更细粒度的缓存控制。
基础使用场景
<template>
<div>
<!-- 基于特定依赖的缓存 -->
<div v-memo="[user.id, user.name]">
<UserProfile :user="user" />
<UserStats :stats="userStats" />
</div>
<!-- 列表项优化 -->
<div
v-for="item in largeList"
:key="item.id"
v-memo="[item.name, item.status, item.priority]"
>
<ComplexListItem :item="item" />
</div>
<!-- 条件渲染优化 -->
<div v-memo="[showDetails, currentTab]">
<DetailPanel v-if="showDetails" :tab="currentTab" />
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const user = ref({
id: 1,
name: 'John',
email: 'john@example.com',
avatar: 'avatar.jpg'
})
const largeList = ref([
// 大量数据...
])
const showDetails = ref(false)
const currentTab = ref('overview')
// 用户统计信息,依赖于用户ID
const userStats = computed(() => {
// 昂贵的计算...
return calculateUserStats(user.value.id)
})
</script>
大型虚拟列表
虚拟列表是处理大数据集的核心技术,通过只渲染可视区域内的元素来保持性能。
基础虚拟滚动实现
// components/VirtualList.vue
<template>
<div
ref="containerRef"
class="virtual-list"
@scroll="handleScroll"
:style="{ height: containerHeight + 'px' }"
>
<!-- 占位符,撑起总高度 -->
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<!-- 可视区域内的元素 -->
<div
:style="{
transform: `translateY(${offsetY}px)`,
position: 'absolute',
top: 0,
left: 0,
right: 0
}"
>
<div
v-for="item in visibleItems"
:key="item.id"
:style="{ height: itemHeight + 'px' }"
class="virtual-item"
>
<slot :item="item" :index="item.originalIndex">
{{ item }}
</slot>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 50
},
containerHeight: {
type: Number,
default: 400
},
overscan: {
type: Number,
default: 5 // 预渲染额外项目数
}
})
const containerRef = ref(null)
const scrollTop = ref(0)
// 计算总高度
const totalHeight = computed(() =>
props.items.length * props.itemHeight
)
// 计算可视区域范围
const visibleRange = computed(() => {
const start = Math.floor(scrollTop.value / props.itemHeight)
const end = Math.min(
start + Math.ceil(props.containerHeight / props.itemHeight),
props.items.length
)
return {
start: Math.max(0, start - props.overscan),
end: Math.min(props.items.length, end + props.overscan)
}
})
// 可视区域内的项目
const visibleItems = computed(() => {
const { start, end } = visibleRange.value
return props.items.slice(start, end).map((item, index) => ({
...item,
originalIndex: start + index
}))
})
// 偏移量
const offsetY = computed(() =>
visibleRange.value.start * props.itemHeight
)
// 滚动处理(节流优化)
let ticking = false
const handleScroll = (event) => {
if (!ticking) {
requestAnimationFrame(() => {
scrollTop.value = event.target.scrollTop
ticking = false
})
ticking = true
}
}
// 定位到指定项目
const scrollToIndex = (index) => {
if (containerRef.value) {
const targetScrollTop = index * props.itemHeight
containerRef.value.scrollTop = targetScrollTop
scrollTop.value = targetScrollTop
}
}
// 暴露方法
defineExpose({
scrollToIndex
})
</script>
<style scoped>
.virtual-list {
overflow-y: auto;
overflow-x: hidden;
}
.virtual-item {
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #eee;
}
</style>
高性能虚拟表格
// components/VirtualTable.vue
<template>
<div class="virtual-table">
<div class="table-header" :style="{ transform: `translateX(-${scrollLeft}px)` }">
<div
v-for="column in columns"
:key="column.key"
:style="{ width: column.width + 'px' }"
class="header-cell"
>
{{ column.title }}
</div>
</div>
<div
ref="tableBodyRef"
class="table-body"
@scroll="handleScroll"
:style="{ height: containerHeight + 'px' }"
>
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<div
:style="{
transform: `translateY(${offsetY}px)`,
position: 'absolute',
top: 0,
left: 0,
right: 0
}"
>
<div
v-for="(row, index) in visibleRows"
:key="row.id"
:style="{ height: rowHeight + 'px' }"
class="table-row"
>
<div
v-for="column in visibleColumns"
:key="column.key"
:style="{
width: column.width + 'px',
left: getColumnLeft(column) + 'px'
}"
class="table-cell"
>
<slot
:name="column.key"
:row="row"
:column="column"
:value="row[column.key]"
>
{{ row[column.key] }}
</slot>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
data: Array,
columns: Array,
rowHeight: { type: Number, default: 40 },
containerHeight: { type: Number, default: 400 },
containerWidth: { type: Number, default: 800 }
})
const tableBodyRef = ref(null)
const scrollTop = ref(0)
const scrollLeft = ref(0)
// 行虚拟化
const totalHeight = computed(() => props.data.length * props.rowHeight)
const visibleRowCount = computed(() =>
Math.ceil(props.containerHeight / props.rowHeight) + 2
)
const startRowIndex = computed(() =>
Math.floor(scrollTop.value / props.rowHeight)
)
const offsetY = computed(() => startRowIndex.value * props.rowHeight)
const visibleRows = computed(() => {
const start = startRowIndex.value
const end = Math.min(start + visibleRowCount.value, props.data.length)
return props.data.slice(start, end)
})
// 列虚拟化
const totalWidth = computed(() =>
props.columns.reduce((sum, col) => sum + col.width, 0)
)
const visibleColumns = computed(() => {
let accumulatedWidth = 0
const result = []
for (const column of props.columns) {
const columnStart = accumulatedWidth
const columnEnd = accumulatedWidth + column.width
// 检查列是否在可视区域内
if (columnEnd >= scrollLeft.value &&
columnStart <= scrollLeft.value + props.containerWidth) {
result.push({
...column,
left: columnStart
})
}
accumulatedWidth += column.width
}
return result
})
const getColumnLeft = (column) => {
return column.left - scrollLeft.value
}
const handleScroll = (event) => {
scrollTop.value = event.target.scrollTop
scrollLeft.value = event.target.scrollLeft
}
</script>
<style scoped>
.virtual-table {
border: 1px solid #ddd;
}
.table-header {
display: flex;
background-color: #f5f5f5;
border-bottom: 1px solid #ddd;
}
.header-cell {
padding: 8px 12px;
border-right: 1px solid #ddd;
font-weight: bold;
flex-shrink: 0;
}
.table-body {
overflow: auto;
position: relative;
}
.table-row {
display: flex;
border-bottom: 1px solid #eee;
position: relative;
}
.table-cell {
padding: 8px 12px;
border-right: 1px solid #eee;
position: absolute;
display: flex;
align-items: center;
}
</style>
减少大型不可变数据的响应性开销
当处理大型数据集时,Vue 的响应式系统可能成为性能瓶颈。通过合理使用浅层响应式和数据优化技术,可以显著提升性能。
浅层响应式优化
shallowReactive 的使用
// 大型数据集的浅层响应式处理
<template>
<div>
<!-- 大型表格数据 -->
<DataTable :data="tableData" />
<!-- 配置面板 -->
<ConfigPanel v-model:config="appConfig" />
</div>
</template>
<script setup>
import { shallowReactive, shallowRef, triggerRef, reactive } from 'vue'
// 对大型数据使用浅层响应式
const tableData = shallowReactive({
items: [], // 大型数组
pagination: { page: 1, size: 50, total: 0 },
filters: {},
sort: { field: 'id', order: 'asc' }
})
// 应用配置使用普通响应式
const appConfig = reactive({
theme: 'light',
language: 'zh-CN',
notifications: true
})
// 批量更新大型数据
const updateTableData = (newData) => {
// 直接替换整个 items 数组
tableData.items = newData.items
tableData.pagination = { ...newData.pagination }
// 手动触发更新
triggerRef(tableData)
}
// 针对性更新单个项目
const updateSingleItem = (index, newItem) => {
// 使用 Object.assign 避免深度响应
Object.assign(tableData.items[index], newItem)
triggerRef(tableData)
}
</script>
大型不可变数据处理
// composables/useImmutableData.js
import { shallowRef, computed, readonly } from 'vue'
export function useImmutableData(initialData) {
const data = shallowRef(initialData)
// 创建只读版本,防止意外修改
const readonlyData = computed(() => readonly(data.value))
// 优化的更新方法
const updateData = (updater) => {
const startTime = performance.now()
// 使用结构化克隆或自定义克隆
const newData = structuredClone ?
structuredClone(data.value) :
deepClone(data.value)
// 应用更新
const result = updater(newData)
data.value = result || newData
const endTime = performance.now()
console.log(`数据更新耗时: ${endTime - startTime}ms`)
}
// 批量更新优化
const batchUpdate = (updates) => {
const newData = { ...data.value }
updates.forEach(update => {
if (typeof update === 'function') {
update(newData)
} else {
Object.assign(newData, update)
}
})
data.value = newData
}
// 局部更新路径
const updatePath = (path, value) => {
const keys = path.split('.')
const newData = { ...data.value }
let current = newData
for (let i = 0; i < keys.length - 1; i++) {
current[keys[i]] = { ...current[keys[i]] }
current = current[keys[i]]
}
current[keys[keys.length - 1]] = value
data.value = newData
}
return {
data: readonlyData,
updateData,
batchUpdate,
updatePath
}
}
数据结构优化
// utils/dataOptimization.js
class OptimizedDataStructure {
constructor(data, options = {}) {
this.options = {
indexFields: [], // 需要建立索引的字段
freezeData: true, // 是否冻结数据
enableSearch: true,
...options
}
this.rawData = data
this.indices = new Map()
this.searchIndex = null
this.initialize()
}
initialize() {
// 冻结数据防止意外修改
if (this.options.freezeData) {
this.frozenData = Object.freeze(
this.rawData.map(item => Object.freeze({ ...item }))
)
}
// 建立索引
this.buildIndices()
// 建立搜索索引
if (this.options.enableSearch) {
this.buildSearchIndex()
}
}
buildIndices() {
this.options.indexFields.forEach(field => {
const index = new Map()
this.rawData.forEach((item, i) => {
const value = item[field]
if (!index.has(value)) {
index.set(value, [])
}
index.get(value).push(i)
})
this.indices.set(field, index)
})
}
buildSearchIndex() {
// 简单的文本搜索索引
this.searchIndex = new Map()
this.rawData.forEach((item, index) => {
const searchText = Object.values(item)
.filter(v => typeof v === 'string')
.join(' ')
.toLowerCase()
const words = searchText.split(/\s+/)
words.forEach(word => {
if (!this.searchIndex.has(word)) {
this.searchIndex.set(word, new Set())
}
this.searchIndex.get(word).add(index)
})
})
}
// 快速查找
findByIndex(field, value) {
const index = this.indices.get(field)
const indices = index?.get(value) || []
return indices.map(i => this.rawData[i])
}
// 范围查询
findInRange(field, min, max) {
return this.rawData.filter(item => {
const value = item[field]
return value >= min && value <= max
})
}
// 文本搜索
search(query) {
if (!this.searchIndex) return []
const words = query.toLowerCase().split(/\s+/)
let resultIndices = null
words.forEach(word => {
const wordIndices = this.searchIndex.get(word)
if (wordIndices) {
if (resultIndices === null) {
resultIndices = new Set(wordIndices)
} else {
// 取交集
resultIndices = new Set(
[...resultIndices].filter(i => wordIndices.has(i))
)
}
} else {
resultIndices = new Set() // 空结果
}
})
return resultIndices ?
[...resultIndices].map(i => this.rawData[i]) : []
}
}
// 使用示例
const optimizedData = new OptimizedDataStructure(largeDataset, {
indexFields: ['category', 'status', 'userId'],
freezeData: true,
enableSearch: true
})
// 快速查询
const userItems = optimizedData.findByIndex('userId', 123)
const activeItems = optimizedData.findByIndex('status', 'active')
const searchResults = optimizedData.search('重要 项目')
避免不必要的组件抽象
过度的组件抽象会带来性能开销,合理的组件设计是优化的关键。
组件抽象层级优化
避免过度包装
// 问题代码 - 过度抽象
<template>
<div>
<WrapperComponent>
<ContainerComponent>
<BoxComponent>
<ContentComponent>
<TextComponent :text="message" />
</ContentComponent>
</BoxComponent>
</ContainerComponent>
</WrapperComponent>
</div>
</template>
// 优化后 - 合理抽象
<template>
<div class="content-container">
<div class="message-box">
{{ message }}
</div>
</div>
</template>
<script setup>
// 只在真正需要逻辑复用时才抽象组件
defineProps(['message'])
</script>
<style scoped>
.content-container {
padding: 16px;
background: #f5f5f5;
}
.message-box {
padding: 12px;
background: white;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
高性能组件设计模式
容器与展示组件分离
// 容器组件 - 负责数据逻辑
// containers/UserListContainer.vue
<template>
<UserListPresentation
:users="users"
:loading="loading"
:error="error"
@user-select="handleUserSelect"
@load-more="loadMoreUsers"
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import UserListPresentation from '@/components/UserListPresentation.vue'
import { useUserApi } from '@/composables/useUserApi'
const { users, loading, error, fetchUsers, loadMore } = useUserApi()
const handleUserSelect = (user) => {
// 业务逻辑处理
console.log('Selected user:', user)
}
const loadMoreUsers = () => {
loadMore()
}
onMounted(() => {
fetchUsers()
})
</script>
// 展示组件 - 纯UI渲染
// components/UserListPresentation.vue
<template>
<div class="user-list">
<div v-if="loading" class="loading">加载中...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else>
<UserCard
v-for="user in users"
:key="user.id"
:user="user"
@click="$emit('user-select', user)"
/>
<button
@click="$emit('load-more')"
class="load-more-btn"
>
加载更多
</button>
</div>
</div>
</template>
<script setup>
// 纯展示组件,无业务逻辑
defineProps(['users', 'loading', 'error'])
defineEmits(['user-select', 'load-more'])
</script>
组件性能监控
// composables/useComponentPerformance.js
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'
export function useComponentPerformance(componentName) {
const renderTimes = ref([])
const updateCount = ref(0)
const averageRenderTime = ref(0)
let mountTime = 0
let updateStartTime = 0
// 监控渲染性能
const startRenderTracking = () => {
updateStartTime = performance.now()
}
const endRenderTracking = () => {
const renderTime = performance.now() - updateStartTime
renderTimes.value.push(renderTime)
updateCount.value++
// 保持最近20次的记录
if (renderTimes.value.length > 20) {
renderTimes.value.shift()
}
// 计算平均渲染时间
averageRenderTime.value = renderTimes.value.reduce((a, b) => a + b, 0) / renderTimes.value.length
// 性能告警
if (renderTime > 50) {
console.warn(`组件 ${componentName} 渲染时间过长: ${renderTime.toFixed(2)}ms`)
}
}
onMounted(() => {
mountTime = performance.now()
endRenderTracking()
})
onUpdated(() => {
endRenderTracking()
})
onUnmounted(() => {
const totalLifetime = performance.now() - mountTime
console.log(`组件 ${componentName} 性能统计:`, {
总生命周期: totalLifetime.toFixed(2) + 'ms',
更新次数: updateCount.value,
平均渲染时间: averageRenderTime.value.toFixed(2) + 'ms',
最慢渲染: Math.max(...renderTimes.value).toFixed(2) + 'ms'
})
})
return {
renderTimes,
updateCount,
averageRenderTime,
startRenderTracking,
endRenderTracking
}
}
组件懒加载与预加载策略
// composables/useSmartComponentLoading.js
import { ref, defineAsyncComponent } from 'vue'
export function useSmartComponentLoading(componentLoader, options = {}) {
const {
preload = false,
priority = 'low',
timeout = 5000,
retries = 3
} = options
const isLoaded = ref(false)
const isLoading = ref(false)
const error = ref(null)
const component = defineAsyncComponent({
loader: async () => {
isLoading.value = true
error.value = null
try {
const comp = await componentLoader()
isLoaded.value = true
return comp
} catch (err) {
error.value = err
throw err
} finally {
isLoading.value = false
}
},
suspensible: true,
timeout,
onError(error, retry, fail, attempts) {
if (attempts <= retries) {
console.warn(`组件加载失败,重试第 ${attempts} 次`)
retry()
} else {
console.error('组件加载最终失败:', error)
fail()
}
}
})
// 预加载逻辑
if (preload) {
const preloadComponent = () => {
if (priority === 'high') {
componentLoader()
} else {
requestIdleCallback(() => {
componentLoader()
})
}
}
// 延迟预加载
setTimeout(preloadComponent, 100)
}
return {
component,
isLoaded,
isLoading,
error
}
}
// 使用示例
<template>
<div>
<Suspense>
<template #default>
<component :is="heavyComponent" v-if="shouldShowComponent" />
</template>
<template #fallback>
<ComponentSkeleton />
</template>
</Suspense>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useSmartComponentLoading } from '@/composables/useSmartComponentLoading'
const shouldShowComponent = ref(false)
const { component: heavyComponent } = useSmartComponentLoading(
() => import('@/components/HeavyComponent.vue'),
{
preload: true,
priority: 'low',
timeout: 10000,
retries: 2
}
)
</script>
通过以上全面的Vue应用性能优化策略,可以显著提升大型Vue应用的运行效率。关键是要根据具体的应用场景选择合适的优化手段,并建立完善的性能监控机制,持续跟踪和改进应用性能。