模式切换
性能与体验优化
减少 setData 的数据量和频率
setData
是小程序开发中最影响性能的 API 之一,因为它涉及视图层与逻辑层的通信和数据序列化。
问题根源
- 通信开销:逻辑层调用
setData
后,数据需要序列化成字符串,通过桥接协议传输到视图层 - 渲染阻塞:过于频繁的
setData
会导致页面渲染卡顿 - 内存压力:大数据量的
setData
会增加内存占用和序列化时间
优化策略
避免一次性设置大量数据:
javascript
// ❌ 不好的做法:设置大量数据
this.setData({
user: hugeUserObject, // 包含数十个字段的用户对象
products: largeProductList, // 包含数百个商品的数组
config: complexConfig // 复杂的配置对象
})
// ✅ 好的做法:按需设置数据
// 只设置页面真正需要的数据
this.setData({
'user.name': userInfo.name,
'user.avatar': userInfo.avatar
})
// 对于列表数据,只设置变化的部分
this.setData({
'products[2].stock': newStock // 只更新第三个商品的库存
})
合并多次 setData
调用:
javascript
// ❌ 不好的做法:频繁调用 setData
this.setData({ loading: true })
this.setData({ list: newList })
this.setData({ page: newPage })
this.setData({ loading: false })
// ✅ 好的做法:合并调用
this.setData({
loading: true,
list: newList,
page: newPage
})
setTimeout(() => {
this.setData({ loading: false })
}, 100)
使用数据 diff 算法:
javascript
// 自定义 diff 函数,只设置变化的数据
function diffAndSet(newData, component) {
const changes = {}
const currentData = component.data
for (let key in newData) {
if (JSON.stringify(currentData[key]) !== JSON.stringify(newData[key])) {
changes[key] = newData[key]
}
}
if (Object.keys(changes).length > 0) {
component.setData(changes)
}
}
// 使用示例
const newUserData = { ...this.data.user, ...updatedFields }
diffAndSet({ user: newUserData }, this)
避免在渲染频繁的场景中使用 setData
:
javascript
// ❌ 不好的做法:在 scroll-view 滚动时频繁 setData
onScroll: function(event) {
this.setData({
scrollTop: event.detail.scrollTop // 这会频繁触发渲染
})
}
// ✅ 好的做法:使用 CSS 或减少更新频率
onScroll: _.throttle(function(event) {
// 使用节流,减少更新频率
this.setData({
scrollTop: event.detail.scrollTop
})
}, 200) // 每200ms最多执行一次
图片优化
图片通常是页面中体积最大的资源,优化图片能显著提升加载速度。
选择合适的图片尺寸
html
<!-- ❌ 不好的做法:使用原图显示缩略图 -->
<image src="https://example.com/huge-image.jpg" mode="aspectFill" style="width: 100px; height: 100px"></image>
<!-- ✅ 好的做法:使用合适尺寸的图片 -->
<image src="https://example.com/huge-image_100x100.jpg" mode="aspectFill" style="width: 100px; height: 100px"></image>
<!-- 或者使用云开发的图片处理 -->
<image src="https://cloud.example.com/huge-image.jpg?imageView2/2/w/200/h/200" style="width: 100px; height: 100px"></image>
实现图片懒加载
html
<!-- 使用小程序自带的懒加载 -->
<image
src="{{imageUrl}}"
lazy-load
mode="widthFix"
bindload="onImageLoad"
binderror="onImageError"
></image>
javascript
// 结合 Intersection Observer 实现自定义懒加载
Page({
data: {
images: [
{ id: 1, src: '', dataSrc: 'https://example.com/image1.jpg', visible: false },
{ id: 2, src: '', dataSrc: 'https://example.com/image2.jpg', visible: false }
]
},
onReady: function() {
// 创建 Intersection Observer
this._observer = wx.createIntersectionObserver(this)
this._observer
.relativeToViewport({ bottom: 100 }) // 提前100px加载
.observe('.lazy-image', (res) => {
if (res.intersectionRatio > 0) {
this.loadImage(res.dataset)
}
})
},
loadImage: function(imageData) {
const index = this.data.images.findIndex(img => img.id === imageData.id)
if (index !== -1 && !this.data.images[index].src) {
this.setData({
[`images[${index}].src`]: imageData.dataSrc,
[`images[${index}].visible`]: true
})
}
},
onUnload: function() {
if (this._observer) {
this._observer.disconnect()
}
}
})
选择合适的图片格式
javascript
// 根据场景选择图片格式
class ImageOptimizer {
static getOptimizedUrl(originalUrl, options = {}) {
const { width, height, quality = 80, format = 'webp' } = options
// 如果是云存储图片,可以添加处理参数
if (originalUrl.includes('cloud.tencent.com')) {
return `${originalUrl}?imageView2/2/w/${width}/h/${height}/q/${quality}/format/${format}`
}
// 其他图床的处理逻辑
return originalUrl
}
}
// 使用示例
const optimizedUrl = ImageOptimizer.getOptimizedUrl(originalImageUrl, {
width: 300,
height: 200,
quality: 75,
format: 'webp' // 优先使用 WebP 格式
})
预加载关键图片
javascript
// 预加载首屏关键图片
Page({
onLoad: function() {
this.preloadCriticalImages()
},
preloadCriticalImages: function() {
const criticalImages = [
'/images/hero-banner.jpg',
'/images/logo.png',
'/images/cta-button.png'
]
criticalImages.forEach(src => {
wx.getImageInfo({
src: src,
success: () => console.log(`预加载成功: ${src}`),
fail: () => console.warn(`预加载失败: ${src}`)
})
})
}
})
使用分包加载机制
分包加载可以将小程序分成多个包,按需加载,显著降低首包大小。
分包配置
json
// app.json
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"subpackages": [
{
"root": "packageA",
"pages": [
"pages/cart/cart",
"pages/order/order"
],
"independent": false // 是否独立分包
},
{
"root": "packageB",
"pages": [
"pages/user/user",
"pages/settings/settings"
],
"independent": true // 独立分包,不依赖主包
},
{
"root": "packageC",
"name": "shop", // 分包别名
"pages": [
"pages/product/list",
"pages/product/detail"
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["packageA"] // 进入首页时预加载 packageA
}
}
}
分包最佳实践
按业务模块分包:
miniprogram/
├── app.js
├── app.json
├── app.wxss
├── pages/ # 主包页面
│ ├── index/
│ └── logs/
├── packageA/ # 购物车分包
│ ├── pages/
│ │ ├── cart/
│ │ └── order/
│ └── components/
├── packageB/ # 用户中心分包
│ ├── pages/
│ │ ├── user/
│ │ └── settings/
│ └── utils/
└── packageC/ # 商品分包
├── pages/
│ ├── product/
│ │ ├── list/
│ │ └── detail/
│ └── category/
└── assets/
独立分包的使用:
javascript
// 独立分包有自己的生命周期,不依赖主包
// packageB/pages/user/user.js
Page({
onLoad: function() {
// 独立分包可以独立运行
console.log('独立分包页面加载')
}
})
预加载策略:
json
// 合理的预加载配置
{
"preloadRule": {
"pages/index/index": {
"packages": ["packageA", "__APP__"] // 预加载常用分包
},
"pages/product/list": {
"packages": ["packageC"] // 进入商品列表时预加载商品详情分包
}
}
}
合理使用缓存
缓存能减少网络请求,提升用户体验,但要合理使用避免数据过期。
数据缓存策略
javascript
class CacheManager {
constructor() {
this.prefix = 'app_'
this.defaultTTL = 5 * 60 * 1000 // 5分钟默认缓存时间
}
// 设置缓存
set(key, data, ttl = this.defaultTTL) {
try {
const cacheData = {
data,
expire: Date.now() + ttl,
timestamp: Date.now()
}
wx.setStorageSync(this.prefix + key, cacheData)
return true
} catch (e) {
console.error('缓存设置失败:', e)
return false
}
}
// 获取缓存
get(key) {
try {
const cached = wx.getStorageSync(this.prefix + key)
if (!cached) return null
// 检查是否过期
if (Date.now() > cached.expire) {
this.remove(key)
return null
}
return cached.data
} catch (e) {
console.error('缓存获取失败:', e)
return null
}
}
// 删除缓存
remove(key) {
try {
wx.removeStorageSync(this.prefix + key)
} catch (e) {
console.error('缓存删除失败:', e)
}
}
// 清空所有缓存
clear() {
try {
const { keys } = wx.getStorageInfoSync()
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
wx.removeStorageSync(key)
}
})
} catch (e) {
console.error('缓存清空失败:', e)
}
}
}
// 使用示例
const cache = new CacheManager()
// 缓存网络请求结果
async function getCachedData(url) {
const cacheKey = `api_${btoa(url)}`
const cached = cache.get(cacheKey)
if (cached) {
return cached // 返回缓存数据
}
// 没有缓存,发起请求
const freshData = await fetchData(url)
cache.set(cacheKey, freshData, 10 * 60 * 1000) // 缓存10分钟
return freshData
}
图片资源缓存
javascript
// 图片缓存管理
class ImageCache {
static async cacheImage(url) {
try {
// 先检查本地是否有缓存
const cacheKey = `img_${btoa(url)}`
const cachedPath = wx.getStorageSync(cacheKey)
if (cachedPath) {
return cachedPath
}
// 下载并缓存图片
const { tempFilePath } = await wx.downloadFile({ url })
wx.setStorageSync(cacheKey, tempFilePath)
return tempFilePath
} catch (error) {
console.error('图片缓存失败:', error)
return url // 降级到使用网络URL
}
}
}
// 使用示例
Page({
loadCachedImage: async function(imageUrl) {
const cachedPath = await ImageCache.cacheImage(imageUrl)
this.setData({ displayedImage: cachedPath })
}
})
请求缓存与更新策略
javascript
// 智能缓存策略
class SmartCache {
static async getWithCache(key, fetchFunction, options = {}) {
const { ttl = 300000, forceRefresh = false } = options
// 强制刷新时直接获取新数据
if (forceRefresh) {
const freshData = await fetchFunction()
cache.set(key, freshData, ttl)
return freshData
}
// 检查缓存
const cached = cache.get(key)
if (cached) {
// 返回缓存数据,但同时在后台更新
this.backgroundUpdate(key, fetchFunction, ttl)
return cached
}
// 没有缓存,获取新数据
const freshData = await fetchFunction()
cache.set(key, freshData, ttl)
return freshData
}
static async backgroundUpdate(key, fetchFunction, ttl) {
try {
const freshData = await fetchFunction()
cache.set(key, freshData, ttl)
} catch (error) {
console.warn('后台更新失败:', error)
}
}
}
// 使用示例
Page({
loadUserProfile: async function() {
const userData = await SmartCache.getWithCache(
'user_profile',
() => this.fetchUserProfileFromAPI(),
{ ttl: 10 * 60 * 1000 } // 缓存10分钟
)
this.setData({ userData })
}
})
缓存清理策略
javascript
// 定期清理过期缓存
function cleanExpiredCache() {
try {
const { keys } = wx.getStorageInfoSync()
const now = Date.now()
keys.forEach(key => {
if (key.startsWith('app_')) {
const cached = wx.getStorageSync(key)
if (cached && now > cached.expire) {
wx.removeStorageSync(key)
}
}
})
} catch (e) {
console.error('缓存清理失败:', e)
}
}
// 在合适的时机清理缓存(如小程序启动时)
App({
onLaunch() {
cleanExpiredCache()
}
})