# 性能优化与最佳实践 ## 💡 核心结论 1. **setData是性能瓶颈,需控制频率和数据量** 2. **图片懒加载和预加载可显著提升体验** 3. **分包加载可突破2MB主包限制** 4. **长列表使用虚拟列表,避免一次性渲染大量节点** 5. **合理使用缓存,减少网络请求** --- ## 1. setData优化 ### 1.1 setData原理 ``` 逻辑层(JS) → 序列化数据 → WebView线程 → 重新渲染 ↑ ↓ └─────────── 耗时过程 ─────────┘ ``` **问题**: - 数据传输有性能开销 - 会阻塞用户交互 - 频繁调用导致卡顿 ### 1.2 优化策略 **1. 只更新必要的数据**: ```javascript // ❌ 错误:更新整个对象 this.setData({ user: { name: '张三', age: 25, address: '北京', avatar: 'https://...' } }) // ✅ 正确:只更新变化的字段 this.setData({ 'user.name': '李四' }) ``` **2. 合并多次setData**: ```javascript // ❌ 错误:多次调用 this.setData({ count: 1 }) this.setData({ name: '张三' }) this.setData({ age: 25 }) // ✅ 正确:合并调用 this.setData({ count: 1, name: '张三', age: 25 }) ``` **3. 控制数据量**: ```javascript // ❌ 错误:传输大量数据 this.setData({ list: hugeArray // 1000个复杂对象 }) // ✅ 正确:分页加载 this.setData({ list: hugeArray.slice(0, 20) }) ``` **4. 防抖和节流**: ```javascript // utils/debounce.js export function debounce(fn, delay = 500) { let timer = null return function(...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args) }, delay) } } // 使用 Page({ onInput: debounce(function(e) { this.setData({ keyword: e.detail.value }) this.search() }, 300) }) ``` **5. 避免在后台态调用**: ```javascript Page({ data: { isActive: true }, onShow() { this.setData({ isActive: true }) }, onHide() { this.setData({ isActive: false }) }, updateData() { // 只在页面激活时更新 if (this.data.isActive) { this.setData({ /* data */ }) } } }) ``` --- ## 2. 长列表优化 ### 2.1 虚拟列表 ```javascript // components/virtual-list/virtual-list.js Component({ properties: { list: { type: Array, value: [] }, itemHeight: { type: Number, value: 100 } }, data: { visibleData: [], startIndex: 0, endIndex: 0, scrollTop: 0 }, lifetimes: { attached() { this.updateVisibleData() } }, methods: { onScroll(e) { const { scrollTop } = e.detail this.setData({ scrollTop }) this.updateVisibleData() }, updateVisibleData() { const { list, itemHeight, scrollTop } = this.data // 计算可见区域 const viewportHeight = wx.getSystemInfoSync().windowHeight const startIndex = Math.floor(scrollTop / itemHeight) const endIndex = Math.ceil((scrollTop + viewportHeight) / itemHeight) // 添加缓冲区 const bufferSize = 5 const start = Math.max(0, startIndex - bufferSize) const end = Math.min(list.length, endIndex + bufferSize) this.setData({ visibleData: list.slice(start, end), startIndex: start, endIndex: end }) } } }) ``` ### 2.2 分页加载 ```javascript Page({ data: { list: [], page: 1, hasMore: true, loading: false }, async onLoad() { await this.loadData() }, async onReachBottom() { if (!this.data.hasMore || this.data.loading) { return } this.setData({ page: this.data.page + 1 }) await this.loadData() }, async loadData() { this.setData({ loading: true }) try { const res = await wx.request({ url: 'https://api.example.com/list', data: { page: this.data.page, size: 20 } }) const newList = res.data.list this.setData({ list: [...this.data.list, ...newList], hasMore: newList.length === 20 }) } finally { this.setData({ loading: false }) } }, // 下拉刷新 async onPullDownRefresh() { this.setData({ list: [], page: 1, hasMore: true }) await this.loadData() wx.stopPullDownRefresh() } }) ``` --- ## 3. 图片优化 ### 3.1 图片懒加载 ```html ``` ```javascript Page({ data: { list: [], placeholderImage: '/images/placeholder.png' }, onScroll(e) { const { scrollTop } = e.detail const viewportHeight = wx.getSystemInfoSync().windowHeight // 计算哪些图片应该加载 this.data.list.forEach((item, index) => { const itemTop = index * 200 // 假设每项高度200rpx const itemBottom = itemTop + 200 if (itemTop < scrollTop + viewportHeight && itemBottom > scrollTop) { this.setData({ [`list[${index}].visible`]: true }) } }) } }) ``` ### 3.2 图片预加载 ```javascript Page({ preloadImages() { const images = [ 'https://example.com/image1.jpg', 'https://example.com/image2.jpg', 'https://example.com/image3.jpg' ] images.forEach(src => { wx.getImageInfo({ src }) }) } }) ``` ### 3.3 图片压缩 ```javascript Page({ async compressImage(filePath) { const res = await wx.compressImage({ src: filePath, quality: 80 // 压缩质量 0-100 }) return res.tempFilePath }, async chooseAndCompress() { const chooseRes = await wx.chooseImage({ count: 1, sizeType: ['compressed'] // 选择压缩图 }) const compressedPath = await this.compressImage(chooseRes.tempFilePaths[0]) // 上传压缩后的图片 this.uploadImage(compressedPath) } }) ``` --- ## 4. 分包加载 ### 4.1 分包配置 ```json // app.json { "pages": [ "pages/index/index", "pages/logs/logs" ], "subPackages": [ { "root": "packageA", "name": "商城", "pages": [ "pages/products/products", "pages/detail/detail", "pages/cart/cart" ] }, { "root": "packageB", "name": "个人中心", "pages": [ "pages/profile/profile", "pages/orders/orders", "pages/settings/settings" ], "independent": true // 独立分包 } ], "preloadRule": { "pages/index/index": { "network": "all", "packages": ["packageA"] } } } ``` ### 4.2 分包跳转 ```javascript // 跳转到分包页面 wx.navigateTo({ url: '/packageA/pages/products/products' }) // 预下载分包 wx.preloadSubpackage({ name: 'packageA', success() { console.log('分包预下载成功') } }) ``` --- ## 5. 缓存策略 ### 5.1 数据缓存 ```javascript // utils/cache.js const CACHE_EXPIRE = 5 * 60 * 1000 // 5分钟 export const cache = { set(key, value, expire = CACHE_EXPIRE) { const data = { value, expire: Date.now() + expire } wx.setStorageSync(key, data) }, get(key) { const data = wx.getStorageSync(key) if (!data) return null if (Date.now() > data.expire) { wx.removeStorageSync(key) return null } return data.value }, remove(key) { wx.removeStorageSync(key) }, clear() { wx.clearStorageSync() } } // 使用 import { cache } from '../../utils/cache.js' Page({ async loadData() { // 先从缓存读取 let data = cache.get('products') if (!data) { // 缓存不存在,请求接口 const res = await wx.request({ url: 'https://api.example.com/products' }) data = res.data // 存入缓存 cache.set('products', data) } this.setData({ products: data }) } }) ``` ### 5.2 图片缓存 ```javascript // 小程序会自动缓存图片 // 使用相同的URL会从缓存读取 // 清除图片缓存 wx.removeImageCache({ success() { console.log('图片缓存已清除') } }) ``` --- ## 6. 代码优化 ### 6.1 减少包体积 **1. 清理无用代码**: ```bash # 使用构建工具 npm run build # 压缩图片 tinypng、imagemin ``` **2. 使用npm包**: ```javascript // 按需引入 import { formatTime } from 'miniprogram-date-utils' // 而不是 import * as utils from 'utils' ``` **3. 代码分离**: ```javascript // 将公共代码抽离到utils // utils/format.js export function formatPrice(price) { return '¥' + (price / 100).toFixed(2) } ``` ### 6.2 避免不必要的渲染 ```html 内容 ``` ### 6.3 使用自定义组件 ```javascript // components/product-item/product-item.js Component({ properties: { product: Object }, methods: { onTap() { this.triggerEvent('tap', { id: this.data.product.id }) } } }) ``` ```html ``` --- ## 7. 性能监控 ### 7.1 性能数据上报 ```javascript // app.js App({ onLaunch() { // 获取性能数据 const performance = wx.getPerformance() // 监听性能数据 const observer = performance.createObserver((entryList) => { console.log('性能数据', entryList.getEntries()) // 上报到后端 this.reportPerformance(entryList.getEntries()) }) observer.observe({ entryTypes: ['navigation', 'render', 'script'] }) }, reportPerformance(entries) { wx.request({ url: 'https://api.example.com/performance', method: 'POST', data: { entries } }) } }) ``` ### 7.2 体验评分 ```javascript // 小程序后台 → 运维中心 → 性能监控 // 可以看到: // - 启动耗时 // - 页面切换耗时 // - 首屏渲染时间 // - 内存占用 // - 卡顿次数 ``` --- ## 8. 最佳实践 ### 8.1 代码规范 ```javascript // 1. 使用ES6+语法 const { list } = this.data const newList = [...list, newItem] // 2. async/await替代回调 async loadData() { const res = await wx.request({ url: '...' }) this.setData({ data: res.data }) } // 3. 合理的注释 /** * 加载商品列表 * @param {number} page 页码 * @param {number} size 每页数量 */ async loadProducts(page, size) { // ... } // 4. 错误处理 try { await this.loadData() } catch (error) { console.error(error) wx.showToast({ title: '加载失败', icon: 'none' }) } ``` ### 8.2 目录结构 ``` miniprogram/ ├── pages/ # 页面 │ ├── index/ │ ├── detail/ │ └── profile/ ├── components/ # 组件 │ ├── product-item/ │ └── user-card/ ├── utils/ # 工具函数 │ ├── request.js │ ├── cache.js │ └── util.js ├── api/ # API接口 │ ├── product.js │ └── user.js ├── styles/ # 公共样式 │ └── common.wxss ├── images/ # 图片资源 └── config/ # 配置文件 └── index.js ``` ### 8.3 用户体验 ```javascript // 1. 加载提示 wx.showLoading({ title: '加载中' }) await loadData() wx.hideLoading() // 3. 错误提示 wx.showToast({ title: '操作失败', icon: 'none', duration: 2000 }) ``` ```html 暂无数据 ``` --- ## 9. 常见问题 ### Q1: 如何调试性能问题? **A**: 1. 开发者工具 → 调试器 → Performance 2. 查看setData调用频率和数据量 3. 使用Trace工具分析 ### Q2: 如何优化首屏加载速度? **A**: 1. 减少首屏请求数量 2. 使用骨架屏 3. 预加载关键资源 4. 启用分包预下载 ### Q3: 如何减少包体积? **A**: 1. 压缩图片资源 2. 使用分包加载 3. 清理无用代码 4. 使用CDN存储静态资源 --- ## 参考资源 - 小程序性能优化指南 - 小程序体验评分规则 - 微信小程序最佳实践