⚡ 性能优化
ERP组件库专为高性能而设计,本指南将帮助您充分发挥组件库的性能潜力,构建快速响应的企业级应用。
🎯 性能目标
我们的性能目标:
- 首屏加载时间 < 2秒
- 页面切换时间 < 500ms
- 大数据表格渲染 < 1秒(10万条数据)
- 内存占用 < 50MB(复杂页面)
- 包体积 < 500KB(gzipped)
📊 性能监控
内置性能监控
组件库内置了性能监控功能:
typescript
import { PerformanceMonitor } from '@erp/common'
// 启用性能监控
PerformanceMonitor.enable({
// 监控配置
trackPageLoad: true,
trackComponentRender: true,
trackUserInteraction: true,
// 上报配置
reportUrl: '/api/performance',
reportInterval: 30000, // 30秒上报一次
// 阈值配置
thresholds: {
pageLoad: 3000, // 页面加载超过3秒告警
componentRender: 100, // 组件渲染超过100ms告警
memoryUsage: 100 // 内存使用超过100MB告警
}
})
// 手动监控特定操作
const monitor = PerformanceMonitor.start('data-processing')
await processLargeDataSet()
monitor.end() // 自动上报耗时性能指标收集
typescript
// 收集核心Web指标
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
getCLS(console.log) // 累积布局偏移
getFID(console.log) // 首次输入延迟
getFCP(console.log) // 首次内容绘制
getLCP(console.log) // 最大内容绘制
getTTFB(console.log) // 首字节时间
// 自定义指标
PerformanceMonitor.measure('table-render-time', () => {
return renderLargeTable()
})🚀 组件级优化
1. 虚拟滚动
对于大数据量场景,使用虚拟滚动:
vue
<template>
<!-- ✅ 推荐:大数据量使用虚拟滚动 -->
<FkTable
:columns="columns"
:data="largeDataSet"
virtual
:item-height="50"
:buffer-size="10"
height="400px"
/>
<!-- ✅ 虚拟列表 -->
<FkVirtualList
:items="items"
:item-height="60"
height="500px"
v-slot="{ item, index }"
>
<UserItem :user="item" :index="index" />
</FkVirtualList>
</template>
<script setup lang="ts">
// 大数据集处理
const largeDataSet = ref<UserData[]>([])
// 分批加载数据
const loadDataInBatches = async () => {
const batchSize = 1000
const totalBatches = Math.ceil(totalCount / batchSize)
for (let i = 0; i < totalBatches; i++) {
const batch = await loadBatch(i, batchSize)
largeDataSet.value.push(...batch)
// 让出主线程,避免阻塞UI
await nextTick()
}
}
</script>2. 懒加载和按需渲染
vue
<template>
<div class="dashboard">
<!-- ✅ 懒加载组件 -->
<Suspense>
<template #default>
<AsyncChart v-if="showChart" :data="chartData" />
</template>
<template #fallback>
<ChartSkeleton />
</template>
</Suspense>
<!-- ✅ 条件渲染 -->
<ExpensiveComponent v-if="shouldRender" />
<!-- ✅ 延迟渲染 -->
<LazyComponent v-show="isVisible" />
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
// 异步组件
const AsyncChart = defineAsyncComponent({
loader: () => import('./Chart.vue'),
loadingComponent: ChartSkeleton,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
// 懒加载组件
const LazyComponent = defineAsyncComponent(() =>
import('./LazyComponent.vue')
)
// 可见性检测
const { isVisible } = useIntersectionObserver(target)
</script>3. 内存优化
typescript
// ✅ 推荐:及时清理资源
export function useResourceCleanup() {
const timers = new Set<number>()
const observers = new Set<IntersectionObserver>()
const subscriptions = new Set<() => void>()
const addTimer = (id: number) => timers.add(id)
const addObserver = (observer: IntersectionObserver) => observers.add(observer)
const addSubscription = (unsubscribe: () => void) => subscriptions.add(unsubscribe)
onUnmounted(() => {
// 清理定时器
timers.forEach(id => clearTimeout(id))
timers.clear()
// 清理观察器
observers.forEach(observer => observer.disconnect())
observers.clear()
// 清理订阅
subscriptions.forEach(unsubscribe => unsubscribe())
subscriptions.clear()
})
return { addTimer, addObserver, addSubscription }
}
// 使用示例
const { addTimer, addObserver } = useResourceCleanup()
// 添加定时器
const timerId = setTimeout(() => {}, 1000)
addTimer(timerId)
// 添加观察器
const observer = new IntersectionObserver(() => {})
addObserver(observer)🎨 渲染优化
1. 减少重渲染
vue
<template>
<!-- ✅ 使用v-memo减少重渲染 -->
<div v-memo="[user.id, user.status]">
<UserCard :user="user" />
</div>
<!-- ✅ 使用key优化列表渲染 -->
<div
v-for="item in items"
:key="`${item.id}-${item.version}`"
>
<ItemCard :item="item" />
</div>
</template>
<script setup lang="ts">
// ✅ 使用shallowRef减少深度响应
const largeObject = shallowRef({
data: new Array(10000).fill(0).map((_, i) => ({ id: i }))
})
// ✅ 使用computed缓存计算结果
const expensiveComputed = computed(() => {
return heavyCalculation(props.data)
})
// ✅ 使用watchEffect优化副作用
watchEffect(() => {
if (props.visible) {
// 只在可见时执行
updateData()
}
})
</script>2. CSS优化
css
/* ✅ 使用transform和opacity进行动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(10px);
}
/* ✅ 使用will-change提示浏览器优化 */
.animated-element {
will-change: transform, opacity;
}
/* ✅ 使用contain优化重排重绘 */
.isolated-component {
contain: layout style paint;
}
/* ✅ 使用GPU加速 */
.gpu-accelerated {
transform: translateZ(0);
backface-visibility: hidden;
}📦 打包优化
1. 代码分割
typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// 框架代码
framework: ['vue', 'vue-router', 'pinia'],
// ERP组件库
'erp-common': ['@erp/common'],
'erp-biz': ['@erp/biz'],
'erp-icons': ['@erp/icons'],
// 工具库
utils: ['lodash-es', 'dayjs', 'axios'],
// 图表库
charts: ['echarts', 'chart.js'],
// 按页面分割
dashboard: ['./src/views/dashboard'],
user: ['./src/views/user'],
order: ['./src/views/order']
}
}
}
}
})
// 路由懒加载
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
{
path: '/user',
component: () => import('./views/User.vue')
}
]2. Tree Shaking
typescript
// ✅ 推荐:按需导入
import { FkInput, FkButton } from '@erp/common'
import { debounce } from 'lodash-es'
import { format } from 'date-fns'
// ❌ 不推荐:全量导入
import * as ERP from '@erp/common'
import _ from 'lodash'
import * as dateFns from 'date-fns'
// ✅ 配置Tree Shaking
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
treeshake: {
moduleSideEffects: false,
propertyReadSideEffects: false,
tryCatchDeoptimization: false
}
}
}
})3. 压缩优化
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
// 打包分析
visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true
})
],
build: {
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log', 'console.info']
},
mangle: {
safari10: true
}
},
// 启用gzip
reportCompressedSize: true,
// 分包大小限制
chunkSizeWarningLimit: 1000
}
})🌐 网络优化
1. 资源预加载
typescript
// 预加载关键资源
const preloadCriticalResources = () => {
// 预加载字体
const fontLink = document.createElement('link')
fontLink.rel = 'preload'
fontLink.href = '/fonts/HarmonyOS-Sans.woff2'
fontLink.as = 'font'
fontLink.type = 'font/woff2'
fontLink.crossOrigin = 'anonymous'
document.head.appendChild(fontLink)
// 预加载关键CSS
const cssLink = document.createElement('link')
cssLink.rel = 'preload'
cssLink.href = '/css/critical.css'
cssLink.as = 'style'
document.head.appendChild(cssLink)
// 预加载关键组件
import('./CriticalComponent.vue')
}
// 预连接外部域名
const preconnectDomains = [
'https://api.example.com',
'https://cdn.example.com'
]
preconnectDomains.forEach(domain => {
const link = document.createElement('link')
link.rel = 'preconnect'
link.href = domain
document.head.appendChild(link)
})2. 缓存策略
typescript
// HTTP缓存配置
const cacheConfig = {
// 静态资源长期缓存
assets: {
'Cache-Control': 'public, max-age=31536000, immutable'
},
// API数据短期缓存
api: {
'Cache-Control': 'public, max-age=300'
},
// HTML文件不缓存
html: {
'Cache-Control': 'no-cache, no-store, must-revalidate'
}
}
// Service Worker缓存
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}
// 内存缓存
const cache = new Map()
export function useCache<T>(key: string, fetcher: () => Promise<T>, ttl = 5 * 60 * 1000) {
const cached = cache.get(key)
if (cached && Date.now() - cached.timestamp < ttl) {
return Promise.resolve(cached.data)
}
return fetcher().then(data => {
cache.set(key, {
data,
timestamp: Date.now()
})
return data
})
}📱 移动端优化
1. 触摸优化
css
/* 优化触摸响应 */
.touch-element {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
/* 优化滚动性能 */
.scroll-container {
-webkit-overflow-scrolling: touch;
overflow-scrolling: touch;
}
/* 减少重绘 */
.optimized-element {
transform: translateZ(0);
will-change: transform;
}2. 响应式图片
vue
<template>
<!-- ✅ 响应式图片 -->
<picture>
<source
media="(max-width: 768px)"
srcset="/images/mobile.webp"
type="image/webp"
>
<source
media="(max-width: 768px)"
srcset="/images/mobile.jpg"
>
<source
srcset="/images/desktop.webp"
type="image/webp"
>
<img
src="/images/desktop.jpg"
alt="描述"
loading="lazy"
decoding="async"
>
</picture>
<!-- ✅ 懒加载图片 -->
<FkImage
:src="imageSrc"
lazy
:placeholder="placeholderSrc"
@load="handleImageLoad"
/>
</template>🔧 开发工具
1. 性能分析工具
typescript
// 性能分析装饰器
export function performanceTrack(name: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
const start = performance.now()
try {
const result = await originalMethod.apply(this, args)
const end = performance.now()
console.log(`[Performance] ${name}: ${end - start}ms`)
// 上报性能数据
PerformanceMonitor.report(name, end - start)
return result
} catch (error) {
const end = performance.now()
console.error(`[Performance] ${name} failed after ${end - start}ms:`, error)
throw error
}
}
return descriptor
}
}
// 使用示例
class UserService {
@performanceTrack('loadUsers')
async loadUsers() {
return await api.get('/users')
}
}2. 内存泄漏检测
typescript
// 内存泄漏检测工具
export class MemoryLeakDetector {
private static instances = new WeakMap()
private static timers = new Set<number>()
private static listeners = new Map<EventTarget, Map<string, EventListener>>()
static trackComponent(component: any, name: string) {
this.instances.set(component, {
name,
createdAt: Date.now(),
stack: new Error().stack
})
}
static trackTimer(id: number) {
this.timers.add(id)
return id
}
static clearTimer(id: number) {
clearTimeout(id)
this.timers.delete(id)
}
static trackListener(target: EventTarget, event: string, listener: EventListener) {
if (!this.listeners.has(target)) {
this.listeners.set(target, new Map())
}
this.listeners.get(target)!.set(event, listener)
}
static removeListener(target: EventTarget, event: string) {
const listeners = this.listeners.get(target)
if (listeners) {
const listener = listeners.get(event)
if (listener) {
target.removeEventListener(event, listener)
listeners.delete(event)
}
}
}
static checkLeaks() {
console.log('Active timers:', this.timers.size)
console.log('Active listeners:', this.listeners.size)
// 检查未清理的定时器
if (this.timers.size > 0) {
console.warn('Potential timer leaks:', Array.from(this.timers))
}
// 检查未清理的事件监听器
if (this.listeners.size > 0) {
console.warn('Potential listener leaks:', this.listeners)
}
}
}
// 在开发环境中使用
if (process.env.NODE_ENV === 'development') {
setInterval(() => {
MemoryLeakDetector.checkLeaks()
}, 10000)
}📊 性能监控面板
vue
<template>
<div v-if="showPerformancePanel" class="performance-panel">
<div class="panel-header">
<h3>性能监控</h3>
<button @click="togglePanel">关闭</button>
</div>
<div class="metrics">
<div class="metric">
<label>FPS</label>
<span :class="{ warning: fps < 30 }">{{ fps }}</span>
</div>
<div class="metric">
<label>内存使用</label>
<span :class="{ warning: memoryUsage > 50 }">{{ memoryUsage }}MB</span>
</div>
<div class="metric">
<label>组件数量</label>
<span>{{ componentCount }}</span>
</div>
<div class="metric">
<label>渲染时间</label>
<span :class="{ warning: renderTime > 16 }">{{ renderTime }}ms</span>
</div>
</div>
<div class="charts">
<canvas ref="fpsChart" width="200" height="100"></canvas>
<canvas ref="memoryChart" width="200" height="100"></canvas>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const showPerformancePanel = ref(false)
const fps = ref(60)
const memoryUsage = ref(0)
const componentCount = ref(0)
const renderTime = ref(0)
let animationId: number
let lastTime = 0
let frameCount = 0
const updateMetrics = () => {
// 计算FPS
const now = performance.now()
frameCount++
if (now - lastTime >= 1000) {
fps.value = Math.round((frameCount * 1000) / (now - lastTime))
frameCount = 0
lastTime = now
}
// 获取内存使用情况
if ('memory' in performance) {
const memory = (performance as any).memory
memoryUsage.value = Math.round(memory.usedJSHeapSize / 1024 / 1024)
}
// 获取组件数量
componentCount.value = document.querySelectorAll('[data-v-]').length
animationId = requestAnimationFrame(updateMetrics)
}
onMounted(() => {
// 快捷键显示性能面板
const handleKeydown = (e: KeyboardEvent) => {
if (e.ctrlKey && e.shiftKey && e.key === 'P') {
showPerformancePanel.value = !showPerformancePanel.value
}
}
document.addEventListener('keydown', handleKeydown)
updateMetrics()
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
cancelAnimationFrame(animationId)
})
})
const togglePanel = () => {
showPerformancePanel.value = false
}
</script>
<style scoped>
.performance-panel {
position: fixed;
top: 20px;
right: 20px;
width: 300px;
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 16px;
border-radius: 8px;
font-family: monospace;
z-index: 10000;
backdrop-filter: blur(10px);
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
}
.metric {
display: flex;
justify-content: space-between;
padding: 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
.metric span.warning {
color: #ff6b6b;
}
.charts {
display: flex;
gap: 8px;
}
.charts canvas {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
</style>🎯 性能优化清单
开发阶段
- [ ] 使用TypeScript进行类型检查
- [ ] 配置ESLint性能规则
- [ ] 使用Vue DevTools分析组件性能
- [ ] 启用Vite的HMR和预构建
- [ ] 使用性能监控装饰器
构建阶段
- [ ] 启用Tree Shaking
- [ ] 配置代码分割策略
- [ ] 压缩JavaScript和CSS
- [ ] 优化图片资源
- [ ] 生成Source Map用于调试
部署阶段
- [ ] 配置CDN加速
- [ ] 启用Gzip/Brotli压缩
- [ ] 设置合理的缓存策略
- [ ] 配置HTTP/2推送
- [ ] 监控Core Web Vitals
运行时优化
- [ ] 使用虚拟滚动处理大数据
- [ ] 实现组件懒加载
- [ ] 优化图片加载策略
- [ ] 减少不必要的重渲染
- [ ] 及时清理资源和事件监听器
通过遵循这些性能优化实践,您可以构建出快速、流畅的ERP应用。记住,性能优化是一个持续的过程,需要在开发的各个阶段都保持关注。
如需更多帮助,请查看: