Skip to content

性能与体验优化

减少 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()
  }
})
编程洪同学服务平台是一个广泛收集编程相关内容和资源,旨在满足编程爱好者和专业开发人员的需求的网站。无论您是初学者还是经验丰富的开发者,都可以在这里找到有用的信息和资料,我们将助您提升编程技能和知识。
专业开发
高端定制
售后无忧
站内资源均为本站制作或收集于互联网等平台,如有侵权,请第一时间联系本站,敬请谅解!本站资源仅限于学习与参考,严禁用于各种非法活动,否则后果自行负责,本站概不承担!