第14集:uni-app 性能优化

章节概述

性能优化是移动应用开发中非常重要的一个环节,它直接影响用户体验和应用的市场竞争力。uni-app 作为一个跨平台开发框架,性能优化显得尤为重要。本章节将详细介绍 uni-app 中的性能优化策略,包括启动优化、渲染优化、网络优化、内存优化等核心知识点,并通过实用案例帮助开发者掌握应用性能优化技巧。

核心知识点

1. 启动优化

启动优化是提高应用用户体验的重要环节,主要包括:

  • 冷启动优化:优化应用首次启动的时间
  • 热启动优化:优化应用从后台切换到前台的时间
  • 启动页优化:优化启动页的显示效果和加载时间

启动优化的具体措施:

  1. 减少应用体积,移除不必要的依赖
  2. 优化首屏渲染,减少首屏加载的资源
  3. 使用骨架屏,提升用户感知
  4. 延迟加载非关键资源
  5. 预加载关键数据

2. 渲染优化

渲染优化是提高应用流畅度的关键,主要包括:

  • 页面渲染优化:优化页面的渲染速度和流畅度
  • 列表渲染优化:优化长列表的渲染性能
  • 动画渲染优化:优化动画的流畅度

渲染优化的具体措施:

  1. 使用虚拟列表,处理长列表渲染
  2. 减少 DOM 节点数量,优化页面结构
  3. 使用 CSS 动画代替 JavaScript 动画
  4. 避免频繁的样式计算和重排
  5. 使用 v-show 代替 v-if,减少 DOM 操作

3. 网络优化

网络优化是提高应用响应速度的重要手段,主要包括:

  • 请求优化:优化网络请求的数量和大小
  • 缓存优化:合理使用缓存,减少重复请求
  • 加载优化:优化资源加载的顺序和方式

网络优化的具体措施:

  1. 合并网络请求,减少请求次数
  2. 使用 HTTP/2,提高请求效率
  3. 启用 Gzip 压缩,减少传输数据量
  4. 使用 CDN,加速静态资源加载
  5. 实现请求缓存,避免重复请求

4. 内存优化

内存优化是提高应用稳定性和避免崩溃的关键,主要包括:

  • 内存使用优化:减少应用的内存使用
  • 内存泄漏优化:避免应用内存泄漏
  • 内存回收优化:促进内存及时回收

内存优化的具体措施:

  1. 及时销毁定时器和事件监听器
  2. 避免循环引用,防止内存泄漏
  3. 优化图片加载,使用适当的图片格式和大小
  4. 减少全局变量的使用
  5. 合理使用 keep-alive,避免过度缓存

实用案例

优化应用启动速度

案例描述

开发一个电商应用,优化应用启动速度,实现以下功能:

  1. 减少应用体积
  2. 优化首屏渲染
  3. 使用骨架屏
  4. 预加载关键数据

代码示例

  1. 减少应用体积
  • 移除不必要的依赖
// package.json
{
  "name": "uni-app-ecommerce",
  "version": "1.0.0",
  "description": "uni-app 电商应用",
  "dependencies": {
    // 只保留必要的依赖
    "@dcloudio/uni-ui": "^1.4.0"
  }
}
  • 按需引入组件
// 按需引入 uni-ui 组件
import { uniBadge, uniButton } from '@dcloudio/uni-ui';

export default {
  components: {
    uniBadge,
    uniButton
  }
};
  1. 优化首屏渲染
  • 使用异步组件
// 异步加载非首屏组件
const ProductDetail = () => import('@/pages/product/detail.vue');
const UserCenter = () => import('@/pages/user/index.vue');

const routes = [
  {
    path: '/product/detail',
    component: ProductDetail
  },
  {
    path: '/user/index',
    component: UserCenter
  }
];
  • 减少首屏请求
// 首屏只请求必要的数据
onLoad() {
  // 只请求轮播图和推荐商品数据
  this.loadBannerData();
  this.loadRecommendProducts();
  
  // 延迟加载其他数据
  setTimeout(() => {
    this.loadCategoryData();
    this.loadHotProducts();
  }, 1000);
};
  1. 使用骨架屏
<template>
  <view class="container">
    <!-- 骨架屏 -->
    <view v-if="loading" class="skeleton">
      <view class="skeleton-banner"></view>
      <view class="skeleton-title"></view>
      <view class="skeleton-list">
        <view class="skeleton-item" v-for="i in 4" :key="i">
          <view class="skeleton-item-img"></view>
          <view class="skeleton-item-info">
            <view class="skeleton-item-title"></view>
            <view class="skeleton-item-desc"></view>
            <view class="skeleton-item-price"></view>
          </view>
        </view>
      </view>
    </view>
    
    <!-- 实际内容 -->
    <view v-else>
      <view class="banner">
        <!-- 轮播图内容 -->
      </view>
      <view class="recommend">
        <!-- 推荐商品内容 -->
      </view>
    </view>
  </view>
</template>

<style>
.skeleton {
  padding: 10px;
}

.skeleton-banner {
  width: 100%;
  height: 150px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 8px;
  margin-bottom: 15px;
}

.skeleton-title {
  width: 60%;
  height: 20px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 4px;
  margin-bottom: 15px;
}

.skeleton-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.skeleton-item {
  width: 48%;
  margin-bottom: 15px;
}

.skeleton-item-img {
  width: 100%;
  height: 120px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 4px;
  margin-bottom: 10px;
}

.skeleton-item-info {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.skeleton-item-title {
  width: 80%;
  height: 14px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 4px;
}

.skeleton-item-desc {
  width: 100%;
  height: 12px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 4px;
}

.skeleton-item-price {
  width: 60%;
  height: 16px;
  background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.5s infinite;
  border-radius: 4px;
}

@keyframes skeleton-loading {
  0% {
    background-position: 200% 0;
  }
  100% {
    background-position: -200% 0;
  }
}
</style>
  1. 预加载关键数据
// 在 App.vue 中预加载关键数据
onLaunch() {
  // 预加载用户信息
  this.preloadUserInfo();
  // 预加载商品分类
  this.preloadCategories();
  // 预加载轮播图数据
  this.preloadBanners();
},
methods: {
  // 预加载用户信息
  preloadUserInfo() {
    uni.request({
      url: 'https://api.example.com/user/info',
      success(res) {
        uni.setStorageSync('userInfo', res.data);
      }
    });
  },
  
  // 预加载商品分类
  preloadCategories() {
    uni.request({
      url: 'https://api.example.com/categories',
      success(res) {
        uni.setStorageSync('categories', res.data);
      }
    });
  },
  
  // 预加载轮播图数据
  preloadBanners() {
    uni.request({
      url: 'https://api.example.com/banners',
      success(res) {
        uni.setStorageSync('banners', res.data);
      }
    });
  }
}

优化列表渲染性能

案例描述

开发一个商品列表页面,优化列表渲染性能,实现以下功能:

  1. 使用虚拟列表,处理长列表渲染
  2. 优化列表项的渲染
  3. 实现列表的下拉刷新和上拉加载

代码示例

  1. 使用虚拟列表
<template>
  <view class="container">
    <virtual-list 
      class="list" 
      :height="listHeight" 
      :item-height="itemHeight" 
      :items="items" 
      :item-count="items.length"
    >
      <template v-slot="{ item, index }">
        <view class="list-item">
          <image class="item-img" :src="item.image" mode="aspectFill"></image>
          <view class="item-info">
            <text class="item-title">{{ item.title }}</text>
            <text class="item-desc">{{ item.desc }}</text>
            <text class="item-price">¥{{ item.price }}</text>
          </view>
        </view>
      </template>
    </virtual-list>
  </view>
</template>

<script>
export default {
  data() {
    return {
      listHeight: 500,
      itemHeight: 100,
      items: []
    };
  },
  onLoad() {
    // 模拟加载数据
    this.loadData();
  },
  methods: {
    loadData() {
      // 模拟 1000 条数据
      const data = [];
      for (let i = 0; i < 1000; i++) {
        data.push({
          id: i,
          title: `商品 ${i}`,
          desc: `商品描述 ${i}`,
          price: Math.random() * 1000,
          image: `https://via.placeholder.com/100x100?text=商品${i}`
        });
      }
      this.items = data;
    }
  }
};
</script>

<style>
.container {
  width: 100%;
  height: 100vh;
  padding: 10px;
}

.list {
  width: 100%;
}

.list-item {
  display: flex;
  padding: 10px;
  border-bottom: 1px solid #f0f0f0;
}

.item-img {
  width: 80px;
  height: 80px;
  border-radius: 4px;
}

.item-info {
  flex: 1;
  margin-left: 10px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.item-title {
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 5px;
}

.item-desc {
  font-size: 14px;
  color: #666;
  margin-bottom: 5px;
}

.item-price {
  font-size: 16px;
  color: #ff4444;
  font-weight: bold;
}
</style>
  1. 优化列表项的渲染
// 使用 computed 缓存计算结果
computed: {
  optimizedItems() {
    return this.items.map(item => ({
      ...item,
      // 缓存计算结果
      formattedPrice: `¥${item.price.toFixed(2)}`,
      // 缓存图片 URL
      optimizedImage: item.image || 'https://via.placeholder.com/100x100?text=默认'
    }));
  }
},

// 使用 v-memo 指令(Vue 3)缓存渲染结果
<template>
  <view 
    v-for="item in optimizedItems" 
    :key="item.id"
    v-memo="[item.id, item.price, item.image]"
  >
    <!-- 列表项内容 -->
  </view>
</template>
  1. 实现列表的下拉刷新和上拉加载
<template>
  <view class="container">
    <scroll-view 
      class="list" 
      scroll-y 
      :style="{ height: listHeight + 'px' }"
      @scrolltolower="loadMore"
      @refresherpulling="onRefresh"
      @refresherrefresh="onRefresh"
      :refresher-enabled="true"
      :refresher-threshold="90"
      :refresher-default-style="'default'"
      :refresher-background="'#f0f0f0'"
    >
      <view v-for="item in items" :key="item.id" class="list-item">
        <image class="item-img" :src="item.image" mode="aspectFill"></image>
        <view class="item-info">
          <text class="item-title">{{ item.title }}</text>
          <text class="item-desc">{{ item.desc }}</text>
          <text class="item-price">¥{{ item.price }}</text>
        </view>
      </view>
      <view v-if="loading" class="loading">加载中...</view>
      <view v-if="noMore" class="no-more">没有更多数据了</view>
    </scroll-view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      listHeight: 500,
      items: [],
      page: 1,
      pageSize: 20,
      loading: false,
      noMore: false
    };
  },
  onLoad() {
    this.loadData();
  },
  methods: {
    // 加载数据
    loadData() {
      this.loading = true;
      uni.request({
        url: 'https://api.example.com/products',
        data: {
          page: this.page,
          pageSize: this.pageSize
        },
        success(res) {
          const data = res.data;
          if (data.length > 0) {
            if (this.page === 1) {
              this.items = data;
            } else {
              this.items = [...this.items, ...data];
            }
            this.page++;
          } else {
            this.noMore = true;
          }
        },
        complete() {
          this.loading = false;
        }
      });
    },
    
    // 加载更多
    loadMore() {
      if (!this.loading && !this.noMore) {
        this.loadData();
      }
    },
    
    // 下拉刷新
    onRefresh() {
      this.page = 1;
      this.noMore = false;
      this.loadData();
    }
  }
};
</script>

常见问题与解决方案

1. 应用启动缓慢

问题:应用启动时间过长,影响用户体验。

解决方案

  • 减少应用体积,移除不必要的依赖
  • 优化首屏渲染,减少首屏加载的资源
  • 使用骨架屏,提升用户感知
  • 延迟加载非关键资源
  • 预加载关键数据

2. 列表渲染卡顿

问题:长列表渲染时出现卡顿现象。

解决方案

  • 使用虚拟列表,处理长列表渲染
  • 优化列表项的渲染,减少 DOM 节点数量
  • 使用 v-memo 指令缓存渲染结果
  • 图片懒加载,避免一次性加载过多图片
  • 减少列表项中的复杂计算

3. 应用内存占用过高

问题:应用内存占用过高,可能导致崩溃。

解决方案

  • 及时销毁定时器和事件监听器
  • 避免循环引用,防止内存泄漏
  • 优化图片加载,使用适当的图片格式和大小
  • 减少全局变量的使用
  • 合理使用 keep-alive,避免过度缓存

4. 网络请求缓慢

问题:网络请求响应时间过长,影响应用响应速度。

解决方案

  • 合并网络请求,减少请求次数
  • 使用 HTTP/2,提高请求效率
  • 启用 Gzip 压缩,减少传输数据量
  • 使用 CDN,加速静态资源加载
  • 实现请求缓存,避免重复请求

学习总结

本章节详细介绍了 uni-app 中的性能优化策略,包括启动优化、渲染优化、网络优化、内存优化等核心知识点,并通过实用案例帮助开发者掌握应用性能优化技巧。通过本章节的学习,开发者应该能够:

  1. 了解 uni-app 性能优化的基本概念和原理
  2. 掌握启动优化的具体措施,提高应用启动速度
  3. 学会渲染优化的方法,提高应用流畅度
  4. 掌握网络优化的技巧,提高应用响应速度
  5. 学会内存优化的方法,提高应用稳定性

性能优化是一个持续的过程,开发者应该在开发过程中不断关注应用的性能表现,及时发现和解决性能问题。通过合理的性能优化策略,可以显著提高应用的用户体验和市场竞争力。

练习与思考

  1. 分析一个 uni-app 应用的性能问题,提出优化方案。
  2. 实现一个使用虚拟列表的长列表页面,优化渲染性能。
  3. 设计一个应用的启动优化方案,提高启动速度。
  4. 实现一个网络请求优化的方案,减少请求次数和响应时间。
  5. 分析内存泄漏的原因,提出避免内存泄漏的方法。
« 上一篇 uni-app 原生能力集成 下一篇 » uni-app 调试与测试