532 lines
9.6 KiB
Markdown
532 lines
9.6 KiB
Markdown
|
|
# 性能优化清单
|
|||
|
|
|
|||
|
|
## 一、首屏加载优化
|
|||
|
|
|
|||
|
|
### 1.1 代码分割
|
|||
|
|
```javascript
|
|||
|
|
// 路由懒加载
|
|||
|
|
const UserProfile = () => import('@/pages/user/profile')
|
|||
|
|
|
|||
|
|
// 组件懒加载
|
|||
|
|
components: {
|
|||
|
|
HeavyComponent: () => import('@/components/HeavyComponent.vue')
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 小程序分包加载
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"subPackages": [
|
|||
|
|
{
|
|||
|
|
"root": "subPackages/order",
|
|||
|
|
"pages": ["list/index", "detail/index"]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"preloadRule": {
|
|||
|
|
"pages/index/index": {
|
|||
|
|
"network": "all",
|
|||
|
|
"packages": ["subPackages/order"]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.3 资源预加载
|
|||
|
|
```javascript
|
|||
|
|
// 预加载关键资源
|
|||
|
|
onLoad() {
|
|||
|
|
// 预加载下一页数据
|
|||
|
|
this.preloadNextPage()
|
|||
|
|
|
|||
|
|
// 预加载图片
|
|||
|
|
uni.preloadImage({
|
|||
|
|
src: '/static/images/banner.jpg'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.4 骨架屏
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<view v-if="loading" class="skeleton">
|
|||
|
|
<view class="skeleton-avatar" />
|
|||
|
|
<view class="skeleton-line" />
|
|||
|
|
<view class="skeleton-line" />
|
|||
|
|
</view>
|
|||
|
|
<view v-else class="content">
|
|||
|
|
<!-- 实际内容 -->
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 二、渲染性能优化
|
|||
|
|
|
|||
|
|
### 2.1 虚拟列表
|
|||
|
|
对于长列表使用虚拟滚动:
|
|||
|
|
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<recycle-list
|
|||
|
|
:list="dataList"
|
|||
|
|
:item-height="100"
|
|||
|
|
>
|
|||
|
|
<template v-slot="{ item }">
|
|||
|
|
<view class="list-item">
|
|||
|
|
{{ item.title }}
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
</recycle-list>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 图片懒加载
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<!-- 小程序端 -->
|
|||
|
|
<image
|
|||
|
|
:src="imageSrc"
|
|||
|
|
lazy-load
|
|||
|
|
mode="aspectFill"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<!-- H5端使用 Intersection Observer -->
|
|||
|
|
<img
|
|||
|
|
v-lazy="imageSrc"
|
|||
|
|
@load="handleImageLoad"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 防抖与节流
|
|||
|
|
```javascript
|
|||
|
|
import { debounce, throttle } from '@/utils/performance'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
methods: {
|
|||
|
|
// 搜索输入防抖
|
|||
|
|
handleSearch: debounce(function(keyword) {
|
|||
|
|
this.search(keyword)
|
|||
|
|
}, 300),
|
|||
|
|
|
|||
|
|
// 滚动事件节流
|
|||
|
|
handleScroll: throttle(function(e) {
|
|||
|
|
this.updateScrollPosition(e)
|
|||
|
|
}, 100)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 条件渲染优化
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<!-- 使用 v-show 代替 v-if(频繁切换) -->
|
|||
|
|
<view v-show="isVisible">内容</view>
|
|||
|
|
|
|||
|
|
<!-- 使用 v-if 代替 v-show(不常切换) -->
|
|||
|
|
<view v-if="hasPermission">内容</view>
|
|||
|
|
|
|||
|
|
<!-- 使用 v-once 渲染静态内容 -->
|
|||
|
|
<view v-once>{{ staticContent }}</view>
|
|||
|
|
</template>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 三、网络请求优化
|
|||
|
|
|
|||
|
|
### 3.1 请求合并
|
|||
|
|
```javascript
|
|||
|
|
// 合并多个接口请求
|
|||
|
|
async loadPageData() {
|
|||
|
|
const [bannerData, menuData, contentData] = await Promise.all([
|
|||
|
|
api.getBanner(),
|
|||
|
|
api.getMenu(),
|
|||
|
|
api.getContent()
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
this.bannerList = bannerData
|
|||
|
|
this.menuList = menuData
|
|||
|
|
this.contentList = contentData
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 请求缓存
|
|||
|
|
```javascript
|
|||
|
|
class RequestCache {
|
|||
|
|
constructor() {
|
|||
|
|
this.cache = new Map()
|
|||
|
|
this.cacheTime = 5 * 60 * 1000 // 5分钟
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
get(key) {
|
|||
|
|
const item = this.cache.get(key)
|
|||
|
|
if (!item) return null
|
|||
|
|
|
|||
|
|
if (Date.now() - item.timestamp > this.cacheTime) {
|
|||
|
|
this.cache.delete(key)
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return item.data
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
set(key, data) {
|
|||
|
|
this.cache.set(key, {
|
|||
|
|
data,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用缓存
|
|||
|
|
const cache = new RequestCache()
|
|||
|
|
|
|||
|
|
export const getUserInfo = async (userId) => {
|
|||
|
|
const cacheKey = `user_${userId}`
|
|||
|
|
const cached = cache.get(cacheKey)
|
|||
|
|
|
|||
|
|
if (cached) return cached
|
|||
|
|
|
|||
|
|
const data = await request.get(`/user/${userId}`)
|
|||
|
|
cache.set(cacheKey, data)
|
|||
|
|
|
|||
|
|
return data
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 请求重试
|
|||
|
|
```javascript
|
|||
|
|
async function requestWithRetry(config, retryCount = 0) {
|
|||
|
|
try {
|
|||
|
|
return await request(config)
|
|||
|
|
} catch (error) {
|
|||
|
|
if (retryCount < 3) {
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|||
|
|
return requestWithRetry(config, retryCount + 1)
|
|||
|
|
}
|
|||
|
|
throw error
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.4 请求取消
|
|||
|
|
```javascript
|
|||
|
|
class RequestManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.pendingRequests = new Map()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
addRequest(key, requestTask) {
|
|||
|
|
// 取消之前的相同请求
|
|||
|
|
if (this.pendingRequests.has(key)) {
|
|||
|
|
this.pendingRequests.get(key).abort()
|
|||
|
|
}
|
|||
|
|
this.pendingRequests.set(key, requestTask)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
removeRequest(key) {
|
|||
|
|
this.pendingRequests.delete(key)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cancelAll() {
|
|||
|
|
this.pendingRequests.forEach(task => task.abort())
|
|||
|
|
this.pendingRequests.clear()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、内存优化
|
|||
|
|
|
|||
|
|
### 4.1 及时清理
|
|||
|
|
```javascript
|
|||
|
|
export default {
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
timer: null,
|
|||
|
|
observer: null
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
beforeUnmount() {
|
|||
|
|
// 清理定时器
|
|||
|
|
if (this.timer) {
|
|||
|
|
clearInterval(this.timer)
|
|||
|
|
this.timer = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理观察器
|
|||
|
|
if (this.observer) {
|
|||
|
|
this.observer.disconnect()
|
|||
|
|
this.observer = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清理事件监听
|
|||
|
|
uni.offNetworkStatusChange()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 大数据处理
|
|||
|
|
```javascript
|
|||
|
|
// 分批处理大数据
|
|||
|
|
function processBigData(data, batchSize = 100) {
|
|||
|
|
const batches = []
|
|||
|
|
for (let i = 0; i < data.length; i += batchSize) {
|
|||
|
|
batches.push(data.slice(i, i + batchSize))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return batches.reduce((promise, batch) => {
|
|||
|
|
return promise.then(() => {
|
|||
|
|
return new Promise(resolve => {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
processBatch(batch)
|
|||
|
|
resolve()
|
|||
|
|
}, 0)
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
}, Promise.resolve())
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 WeakMap 使用
|
|||
|
|
```javascript
|
|||
|
|
// 使用 WeakMap 避免内存泄漏
|
|||
|
|
const cache = new WeakMap()
|
|||
|
|
|
|||
|
|
function cacheData(obj, data) {
|
|||
|
|
cache.set(obj, data)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getCachedData(obj) {
|
|||
|
|
return cache.get(obj)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 五、图片优化
|
|||
|
|
|
|||
|
|
### 5.1 图片压缩
|
|||
|
|
```javascript
|
|||
|
|
// 上传前压缩图片
|
|||
|
|
function compressImage(filePath) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
uni.compressImage({
|
|||
|
|
src: filePath,
|
|||
|
|
quality: 80,
|
|||
|
|
success: (res) => resolve(res.tempFilePath),
|
|||
|
|
fail: reject
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 图片格式选择
|
|||
|
|
- **照片**:使用 JPEG 格式
|
|||
|
|
- **图标**:使用 PNG 或 SVG 格式
|
|||
|
|
- **动画**:使用 GIF 或 WebP 格式
|
|||
|
|
- **透明背景**:使用 PNG 格式
|
|||
|
|
|
|||
|
|
### 5.3 响应式图片
|
|||
|
|
```vue
|
|||
|
|
<template>
|
|||
|
|
<image
|
|||
|
|
:src="getImageUrl()"
|
|||
|
|
mode="aspectFill"
|
|||
|
|
/>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
export default {
|
|||
|
|
methods: {
|
|||
|
|
getImageUrl() {
|
|||
|
|
const systemInfo = uni.getSystemInfoSync()
|
|||
|
|
const dpr = systemInfo.pixelRatio
|
|||
|
|
|
|||
|
|
// 根据设备像素比选择图片
|
|||
|
|
if (dpr >= 3) {
|
|||
|
|
return this.image + '@3x.jpg'
|
|||
|
|
} else if (dpr >= 2) {
|
|||
|
|
return this.image + '@2x.jpg'
|
|||
|
|
}
|
|||
|
|
return this.image + '.jpg'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.4 图片预加载
|
|||
|
|
```javascript
|
|||
|
|
function preloadImages(urls) {
|
|||
|
|
return Promise.all(
|
|||
|
|
urls.map(url => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
uni.getImageInfo({
|
|||
|
|
src: url,
|
|||
|
|
success: resolve,
|
|||
|
|
fail: reject
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 六、小程序特定优化
|
|||
|
|
|
|||
|
|
### 6.1 setData 优化
|
|||
|
|
```javascript
|
|||
|
|
// 避免频繁 setData
|
|||
|
|
let updateTimer = null
|
|||
|
|
let pendingData = {}
|
|||
|
|
|
|||
|
|
function batchUpdate(data) {
|
|||
|
|
Object.assign(pendingData, data)
|
|||
|
|
|
|||
|
|
if (updateTimer) return
|
|||
|
|
|
|||
|
|
updateTimer = setTimeout(() => {
|
|||
|
|
this.setData(pendingData)
|
|||
|
|
pendingData = {}
|
|||
|
|
updateTimer = null
|
|||
|
|
}, 16) // 约60fps
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 避免 setData 传递大数据
|
|||
|
|
// 不好的做法
|
|||
|
|
this.setData({
|
|||
|
|
list: this.data.list.concat(newList)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 好的做法
|
|||
|
|
this.setData({
|
|||
|
|
[`list[${this.data.list.length}]`]: newItem
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 自定义组件
|
|||
|
|
```javascript
|
|||
|
|
// 使用自定义组件代替模板
|
|||
|
|
Component({
|
|||
|
|
options: {
|
|||
|
|
// 启用纯数据字段
|
|||
|
|
pureDataPattern: /^_/,
|
|||
|
|
// 启用多slot支持
|
|||
|
|
multipleSlots: true
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
properties: {
|
|||
|
|
title: String
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
data: {
|
|||
|
|
_privateData: 'private' // 纯数据字段,不参与渲染
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.3 分包预下载
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"preloadRule": {
|
|||
|
|
"pages/index/index": {
|
|||
|
|
"network": "all",
|
|||
|
|
"packages": ["subPackages/order"]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 七、H5 特定优化
|
|||
|
|
|
|||
|
|
### 7.1 PWA 支持
|
|||
|
|
```javascript
|
|||
|
|
// 注册 Service Worker
|
|||
|
|
if ('serviceWorker' in navigator) {
|
|||
|
|
navigator.serviceWorker.register('/sw.js')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// sw.js
|
|||
|
|
self.addEventListener('install', (event) => {
|
|||
|
|
event.waitUntil(
|
|||
|
|
caches.open('v1').then((cache) => {
|
|||
|
|
return cache.addAll([
|
|||
|
|
'/',
|
|||
|
|
'/static/css/main.css',
|
|||
|
|
'/static/js/main.js'
|
|||
|
|
])
|
|||
|
|
})
|
|||
|
|
)
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 CDN 加速
|
|||
|
|
```javascript
|
|||
|
|
// 静态资源使用 CDN
|
|||
|
|
const CDN_URL = 'https://cdn.example.com'
|
|||
|
|
|
|||
|
|
export const getStaticUrl = (path) => {
|
|||
|
|
return `${CDN_URL}${path}`
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.3 Gzip 压缩
|
|||
|
|
```javascript
|
|||
|
|
// vite.config.js
|
|||
|
|
import viteCompression from 'vite-plugin-compression'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
plugins: [
|
|||
|
|
viteCompression({
|
|||
|
|
algorithm: 'gzip',
|
|||
|
|
ext: '.gz'
|
|||
|
|
})
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 八、性能监控
|
|||
|
|
|
|||
|
|
### 8.1 性能指标收集
|
|||
|
|
```javascript
|
|||
|
|
// 页面加载时间
|
|||
|
|
const pageLoadTime = performance.timing.loadEventEnd - performance.timing.navigationStart
|
|||
|
|
|
|||
|
|
// 首屏渲染时间
|
|||
|
|
const firstPaintTime = performance.getEntriesByType('paint')[0].startTime
|
|||
|
|
|
|||
|
|
// 接口请求时间
|
|||
|
|
const apiTime = performance.getEntriesByType('resource')
|
|||
|
|
.filter(item => item.initiatorType === 'xmlhttprequest')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 性能上报
|
|||
|
|
```javascript
|
|||
|
|
function reportPerformance(data) {
|
|||
|
|
// 上报到监控平台
|
|||
|
|
uni.request({
|
|||
|
|
url: 'https://monitor.example.com/report',
|
|||
|
|
method: 'POST',
|
|||
|
|
data: {
|
|||
|
|
type: 'performance',
|
|||
|
|
...data
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 九、优化检查清单
|
|||
|
|
|
|||
|
|
- [ ] 启用代码分割和懒加载
|
|||
|
|
- [ ] 实现图片懒加载
|
|||
|
|
- [ ] 使用虚拟列表处理长列表
|
|||
|
|
- [ ] 添加请求缓存机制
|
|||
|
|
- [ ] 实现请求重试机制
|
|||
|
|
- [ ] 合并并发请求
|
|||
|
|
- [ ] 压缩图片资源
|
|||
|
|
- [ ] 使用 CDN 加速静态资源
|
|||
|
|
- [ ] 启用 Gzip 压缩
|
|||
|
|
- [ ] 小程序分包加载
|
|||
|
|
- [ ] 优化 setData 调用
|
|||
|
|
- [ ] 及时清理定时器和监听器
|
|||
|
|
- [ ] 使用防抖和节流
|
|||
|
|
- [ ] 添加骨架屏
|
|||
|
|
- [ ] 实现性能监控
|