uni-app 小程序性能优化

核心知识点

1. 小程序运行机制

了解小程序的运行机制是优化性能的基础:

  • 双线程架构:渲染层(WebView)和逻辑层(JavaScriptCore)分离
  • 生命周期:启动、显示、隐藏、销毁等阶段
  • 内存限制:小程序运行内存有限,需要合理管理
  • 网络限制:部分平台对网络请求有并发限制

2. 性能优化策略

有效的小程序性能优化策略包括:

  • 启动优化:减少启动时间,提升首屏加载速度
  • 渲染优化:提高页面渲染性能,减少卡顿
  • 网络优化:优化网络请求,减少数据传输时间
  • 内存优化:合理管理内存,避免内存泄漏
  • 代码优化:减少代码体积,提高代码执行效率

3. 性能监控工具

常用的小程序性能监控工具:

  • 微信开发者工具:性能分析面板
  • uni-app 开发者工具:性能监控功能
  • 第三方监控平台:如腾讯云、阿里云的小程序监控服务

实用案例分析

案例:小程序性能优化实战

1. 启动优化

优化策略

  • 减少主包体积:使用分包加载,将非核心页面放入分包
  • 资源预加载:预加载可能需要的资源和数据
  • 缓存策略:合理使用本地缓存,减少重复请求
  • 代码压缩:开启代码压缩,减少代码体积

实现代码

App.vue 中的启动优化

<script>
export default {
  onLaunch() {
    // 1. 预加载用户信息
    this.preloadUserInfo();
    
    // 2. 预加载常用数据
    this.preloadCommonData();
    
    // 3. 检查更新
    this.checkUpdate();
  },
  methods: {
    preloadUserInfo() {
      // 从缓存获取用户信息
      const userInfo = uni.getStorageSync('userInfo');
      if (userInfo) {
        // 将用户信息存储到全局状态
        getApp().globalData.userInfo = userInfo;
      }
    },
    
    preloadCommonData() {
      // 预加载分类数据
      uni.request({
        url: 'https://api.example.com/categories',
        success: (res) => {
          if (res.data.code === 0) {
            uni.setStorageSync('categories', res.data.data);
            getApp().globalData.categories = res.data.data;
          }
        },
        fail: () => {
          // 失败时从缓存获取
          const categories = uni.getStorageSync('categories');
          if (categories) {
            getApp().globalData.categories = categories;
          }
        }
      });
    },
    
    checkUpdate() {
      // 检查小程序更新
      if (uni.canIUse('getUpdateManager')) {
        const updateManager = uni.getUpdateManager();
        updateManager.onCheckForUpdate((res) => {
          if (res.hasUpdate) {
            updateManager.onUpdateReady(() => {
              uni.showModal({
                title: '更新提示',
                content: '新版本已准备就绪,是否重启应用?',
                success: (res) => {
                  if (res.confirm) {
                    updateManager.applyUpdate();
                  }
                }
              });
            });
          }
        });
      }
    }
  }
};
</script>

2. 渲染优化

优化策略

  • 使用虚拟列表:长列表使用虚拟列表,减少DOM节点
  • 避免频繁渲染:合理使用 v-ifv-show,避免不必要的渲染
  • 使用计算属性:复杂计算使用计算属性,避免重复计算
  • 减少 setData:减少 setData 调用次数和数据量
  • 使用 CSS 动画:优先使用 CSS 动画,避免 JavaScript 动画

实现代码

虚拟列表组件 (components/virtual-list.vue):

<template>
  <view class="virtual-list" :style="{ height: containerHeight + 'px' }">
    <view class="list-container" :style="{ transform: `translateY(${offsetTop}px)` }">
      <view 
        v-for="item in visibleItems" 
        :key="item.id" 
        class="list-item"
        :style="{ height: itemHeight + 'px' }"
      >
        <slot :item="item"></slot>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      default: () => []
    },
    itemHeight: {
      type: Number,
      default: 100
    },
    containerHeight: {
      type: Number,
      default: 500
    }
  },
  data() {
    return {
      scrollTop: 0,
      startIndex: 0,
      endIndex: 0,
      offsetTop: 0
    };
  },
  computed: {
    visibleItems() {
      return this.items.slice(this.startIndex, this.endIndex + 1);
    },
    visibleCount() {
      return Math.ceil(this.containerHeight / this.itemHeight) + 2;
    }
  },
  mounted() {
    this.updateVisibleRange();
    this.bindScrollEvent();
  },
  methods: {
    bindScrollEvent() {
      const container = this.$el;
      container.addEventListener('scroll', this.handleScroll);
    },
    
    handleScroll(e) {
      this.scrollTop = e.target.scrollTop;
      this.updateVisibleRange();
    },
    
    updateVisibleRange() {
      this.startIndex = Math.floor(this.scrollTop / this.itemHeight);
      this.endIndex = Math.min(this.startIndex + this.visibleCount - 1, this.items.length - 1);
      this.offsetTop = this.startIndex * this.itemHeight;
    }
  }
};
</script>

<style scoped>
.virtual-list {
  overflow-y: auto;
  position: relative;
}

.list-container {
  position: relative;
}

.list-item {
  box-sizing: border-box;
  border-bottom: 1rpx solid #eee;
  padding: 20rpx;
}
</style>

使用虚拟列表 (pages/goods/list.vue):

<template>
  <view class="goods-list-page">
    <virtual-list 
      :items="goodsList" 
      :item-height="150" 
      :container-height="600"
    >
      <template v-slot:default="{ item }">
        <view class="goods-item">
          <image :src="item.image" class="goods-image"></image>
          <view class="goods-info">
            <text class="goods-name">{{ item.name }}</text>
            <text class="goods-price">¥{{ item.price }}</text>
          </view>
        </view>
      </template>
    </virtual-list>
  </view>
</template>

<script>
import VirtualList from '@/components/virtual-list.vue';

export default {
  components: {
    VirtualList
  },
  data() {
    return {
      goodsList: []
    };
  },
  onLoad() {
    this.loadGoodsList();
  },
  methods: {
    loadGoodsList() {
      // 模拟加载商品列表
      const list = [];
      for (let i = 0; i < 1000; i++) {
        list.push({
          id: i,
          name: `商品${i}`,
          price: Math.random() * 1000,
          image: `https://via.placeholder.com/100`
        });
      }
      this.goodsList = list;
    }
  }
};
</script>

<style scoped>
.goods-list-page {
  padding: 20rpx;
}

.goods-item {
  display: flex;
  align-items: center;
  height: 150rpx;
}

.goods-image {
  width: 120rpx;
  height: 120rpx;
  border-radius: 5rpx;
  margin-right: 20rpx;
}

.goods-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 120rpx;
}

.goods-name {
  font-size: 32rpx;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.goods-price {
  font-size: 32rpx;
  color: #ff4d4f;
  font-weight: 500;
}
</style>

3. 网络优化

优化策略

  • 请求合并:合并多个请求,减少网络请求次数
  • 数据缓存:合理使用本地缓存,减少重复请求
  • 图片优化:使用适当尺寸的图片,支持 WebP 格式
  • CDN 加速:使用 CDN 加速静态资源
  • HTTP/2:使用 HTTP/2,提高传输效率

实现代码

网络请求封装 (utils/request.js):

// 网络请求封装
const request = {
  // 缓存对象
  cache: {},
  
  // 请求方法
  async get(url, params = {}, options = {}) {
    const { cache = false, cacheTime = 5 * 60 * 1000 } = options;
    const cacheKey = url + JSON.stringify(params);
    
    // 检查缓存
    if (cache && this.cache[cacheKey]) {
      const cachedData = this.cache[cacheKey];
      if (Date.now() - cachedData.timestamp < cacheTime) {
        return cachedData.data;
      }
    }
    
    // 发起请求
    const res = await uni.request({
      url,
      method: 'GET',
      data: params,
      header: {
        'Content-Type': 'application/json'
      }
    });
    
    // 缓存数据
    if (cache && res.data.code === 0) {
      this.cache[cacheKey] = {
        data: res.data,
        timestamp: Date.now()
      };
    }
    
    return res.data;
  },
  
  // 并发请求控制
  async all(requests) {
    return Promise.all(requests);
  },
  
  // 清理缓存
  clearCache() {
    this.cache = {};
  }
};

export default request;

使用网络请求封装 (pages/index/index.vue):

<script>
import request from '@/utils/request';

export default {
  data() {
    return {
      banners: [],
      categories: [],
      hotGoods: []
    };
  },
  onLoad() {
    this.loadHomeData();
  },
  methods: {
    async loadHomeData() {
      try {
        // 并发请求,减少请求时间
        const [bannersRes, categoriesRes, hotGoodsRes] = await request.all([
          request.get('https://api.example.com/banners', {}, { cache: true }),
          request.get('https://api.example.com/categories', {}, { cache: true }),
          request.get('https://api.example.com/hot-goods', {}, { cache: true })
        ]);
        
        if (bannersRes.code === 0) {
          this.banners = bannersRes.data;
        }
        
        if (categoriesRes.code === 0) {
          this.categories = categoriesRes.data;
        }
        
        if (hotGoodsRes.code === 0) {
          this.hotGoods = hotGoodsRes.data;
        }
      } catch (error) {
        console.error('加载首页数据失败:', error);
      }
    }
  }
};
</script>

4. 内存优化

优化策略

  • 及时清理定时器:页面卸载时清理定时器
  • 避免循环引用:避免对象之间的循环引用
  • 合理使用事件监听器:及时移除不需要的事件监听器
  • 减少全局变量:减少全局变量的使用,避免内存泄漏
  • 使用 WeakMap:对于临时对象,使用 WeakMap 存储

实现代码

页面中的内存优化 (pages/detail/detail.vue):

<script>
export default {
  data() {
    return {
      timer: null,
      scrollListener: null
    };
  },
  onLoad() {
    this.startTimer();
    this.bindScrollListener();
  },
  onUnload() {
    this.clearTimer();
    this.removeScrollListener();
  },
  methods: {
    startTimer() {
      // 启动定时器
      this.timer = setInterval(() => {
        console.log('Timer running...');
      }, 1000);
    },
    
    clearTimer() {
      // 清理定时器
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }
    },
    
    bindScrollListener() {
      // 绑定滚动事件
      this.scrollListener = (e) => {
        console.log('Scroll position:', e.scrollTop);
      };
      window.addEventListener('scroll', this.scrollListener);
    },
    
    removeScrollListener() {
      // 移除滚动事件
      if (this.scrollListener) {
        window.removeEventListener('scroll', this.scrollListener);
        this.scrollListener = null;
      }
    }
  }
};
</script>

5. 代码优化

优化策略

  • 代码分割:使用分包加载,减少主包体积
  • 按需加载:按需加载第三方库,减少初始加载时间
  • 减少冗余代码:删除无用代码,减少代码体积
  • 使用 ES6+ 特性:使用 ES6+ 特性,提高代码执行效率
  • 代码压缩:开启代码压缩,减少代码体积

实现代码

按需加载第三方库

// 按需加载 lodash
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';

// 使用按需加载的函数
export default {
  methods: {
    // 防抖函数
    debouncedSearch: debounce(function(query) {
      // 搜索逻辑
    }, 300),
    
    // 节流函数
    throttledScroll: throttle(function() {
      // 滚动逻辑
    }, 100)
  }
};

学习目标

通过本章节的学习,你应该能够:

  1. 理解小程序运行机制:掌握小程序的双线程架构和生命周期
  2. 掌握性能优化策略:能够从启动、渲染、网络、内存等方面优化小程序性能
  3. 使用性能监控工具:能够使用开发者工具和第三方监控平台分析性能问题
  4. 实现性能优化:能够根据项目特点,实施有效的性能优化方案
  5. 持续监控性能:建立性能监控机制,持续优化小程序性能

总结

小程序性能优化是一个持续的过程,需要从多个方面入手,综合考虑各种因素。通过合理的优化策略,可以显著提升小程序的用户体验,提高用户留存率。

在实际项目中,我们需要注意以下几点:

  1. 性能分析:使用性能监控工具,定期分析小程序性能
  2. 优化优先级:根据用户体验影响程度,确定优化优先级
  3. 持续优化:建立性能优化的持续迭代机制
  4. 测试验证:优化后进行充分测试,确保优化效果
  5. 用户反馈:关注用户反馈,及时解决性能问题

通过本章节的学习和实践,你应该能够熟练掌握 uni-app 小程序性能优化的方法和技巧,为用户提供更加流畅、快速的应用体验。

« 上一篇 uni-app 小程序分包加载 下一篇 » uni-app App 性能优化