第14集:uni-app 性能优化
章节概述
性能优化是移动应用开发中非常重要的一个环节,它直接影响用户体验和应用的市场竞争力。uni-app 作为一个跨平台开发框架,性能优化显得尤为重要。本章节将详细介绍 uni-app 中的性能优化策略,包括启动优化、渲染优化、网络优化、内存优化等核心知识点,并通过实用案例帮助开发者掌握应用性能优化技巧。
核心知识点
1. 启动优化
启动优化是提高应用用户体验的重要环节,主要包括:
- 冷启动优化:优化应用首次启动的时间
- 热启动优化:优化应用从后台切换到前台的时间
- 启动页优化:优化启动页的显示效果和加载时间
启动优化的具体措施:
- 减少应用体积,移除不必要的依赖
- 优化首屏渲染,减少首屏加载的资源
- 使用骨架屏,提升用户感知
- 延迟加载非关键资源
- 预加载关键数据
2. 渲染优化
渲染优化是提高应用流畅度的关键,主要包括:
- 页面渲染优化:优化页面的渲染速度和流畅度
- 列表渲染优化:优化长列表的渲染性能
- 动画渲染优化:优化动画的流畅度
渲染优化的具体措施:
- 使用虚拟列表,处理长列表渲染
- 减少 DOM 节点数量,优化页面结构
- 使用 CSS 动画代替 JavaScript 动画
- 避免频繁的样式计算和重排
- 使用
v-show代替v-if,减少 DOM 操作
3. 网络优化
网络优化是提高应用响应速度的重要手段,主要包括:
- 请求优化:优化网络请求的数量和大小
- 缓存优化:合理使用缓存,减少重复请求
- 加载优化:优化资源加载的顺序和方式
网络优化的具体措施:
- 合并网络请求,减少请求次数
- 使用 HTTP/2,提高请求效率
- 启用 Gzip 压缩,减少传输数据量
- 使用 CDN,加速静态资源加载
- 实现请求缓存,避免重复请求
4. 内存优化
内存优化是提高应用稳定性和避免崩溃的关键,主要包括:
- 内存使用优化:减少应用的内存使用
- 内存泄漏优化:避免应用内存泄漏
- 内存回收优化:促进内存及时回收
内存优化的具体措施:
- 及时销毁定时器和事件监听器
- 避免循环引用,防止内存泄漏
- 优化图片加载,使用适当的图片格式和大小
- 减少全局变量的使用
- 合理使用
keep-alive,避免过度缓存
实用案例
优化应用启动速度
案例描述
开发一个电商应用,优化应用启动速度,实现以下功能:
- 减少应用体积
- 优化首屏渲染
- 使用骨架屏
- 预加载关键数据
代码示例
- 减少应用体积
- 移除不必要的依赖
// 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
}
};- 优化首屏渲染
- 使用异步组件
// 异步加载非首屏组件
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);
};- 使用骨架屏
<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>- 预加载关键数据
// 在 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);
}
});
}
}优化列表渲染性能
案例描述
开发一个商品列表页面,优化列表渲染性能,实现以下功能:
- 使用虚拟列表,处理长列表渲染
- 优化列表项的渲染
- 实现列表的下拉刷新和上拉加载
代码示例
- 使用虚拟列表
<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>- 优化列表项的渲染
// 使用 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>- 实现列表的下拉刷新和上拉加载
<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 中的性能优化策略,包括启动优化、渲染优化、网络优化、内存优化等核心知识点,并通过实用案例帮助开发者掌握应用性能优化技巧。通过本章节的学习,开发者应该能够:
- 了解 uni-app 性能优化的基本概念和原理
- 掌握启动优化的具体措施,提高应用启动速度
- 学会渲染优化的方法,提高应用流畅度
- 掌握网络优化的技巧,提高应用响应速度
- 学会内存优化的方法,提高应用稳定性
性能优化是一个持续的过程,开发者应该在开发过程中不断关注应用的性能表现,及时发现和解决性能问题。通过合理的性能优化策略,可以显著提高应用的用户体验和市场竞争力。
练习与思考
- 分析一个 uni-app 应用的性能问题,提出优化方案。
- 实现一个使用虚拟列表的长列表页面,优化渲染性能。
- 设计一个应用的启动优化方案,提高启动速度。
- 实现一个网络请求优化的方案,减少请求次数和响应时间。
- 分析内存泄漏的原因,提出避免内存泄漏的方法。