性能优化是 UniApp 开发中的重要环节。一个性能优秀的应用不仅能提供流畅的用户体验,还能节省设备资源。本文将分享 UniApp 性能优化的实用技巧。
1. 渲染优化
使用 v-show 替代 v-if
<template>
<!-- ❌ 不推荐:频繁切换会销毁和创建组件 -->
<view v-if="show">内容</view>
<!-- ✅ 推荐:只切换显示隐藏,不销毁组件 -->
<view v-show="show">内容</view>
</template>
合理使用 key
<template>
<!-- ✅ 使用唯一且稳定的 key -->
<view v-for="item in list" :key="item.id">
{{ item.name }}
</view>
</template>
避免在模板中使用复杂计算
<template>
<!-- ❌ 不推荐:每次渲染都计算 -->
<view>{{ complexCalculation() }}</view>
<!-- ✅ 推荐:使用 computed -->
<view>{{ computedValue }}</view>
</template>
<script>
export default {
data() {
return {
list: []
}
},
computed: {
computedValue() {
// 复杂计算,结果会被缓存
return this.list.reduce((sum, item) => sum + item.value, 0)
}
}
}
</script>
使用 Object.freeze 冻结数据
export default {
data() {
return {
// 冻结大列表数据,避免响应式处理
largeList: Object.freeze([
// 大量数据
])
}
}
}
2. 列表优化
使用虚拟列表
<template>
<!-- 长列表使用 scroll-view -->
<scroll-view
scroll-y
class="scroll-container"
:scroll-top="scrollTop"
@scroll="handleScroll"
>
<view
v-for="item in visibleItems"
:key="item.id"
class="list-item"
>
{{ item.name }}
</view>
</scroll-view>
</template>
<script>
export default {
data() {
return {
allItems: [],
visibleItems: [],
scrollTop: 0,
itemHeight: 100, // 每个列表项的高度
containerHeight: 0
}
},
onLoad() {
this.initList()
this.calculateVisibleItems()
},
methods: {
initList() {
// 初始化列表数据
this.allItems = Array.from({ length: 1000 }, (_, i) => ({
id: i,
name: `项目 ${i}`
}))
},
calculateVisibleItems() {
const start = Math.floor(this.scrollTop / this.itemHeight)
const end = start + Math.ceil(this.containerHeight / this.itemHeight) + 1
this.visibleItems = this.allItems.slice(start, end)
},
handleScroll(e) {
this.scrollTop = e.detail.scrollTop
this.calculateVisibleItems()
}
}
}
</script>
分页加载
<template>
<scroll-view
scroll-y
@scrolltolower="loadMore"
:lower-threshold="100"
>
<view v-for="item in list" :key="item.id">
{{ item.name }}
</view>
<view v-if="loading" class="loading">加载中...</view>
<view v-if="noMore" class="no-more">没有更多了</view>
</scroll-view>
</template>
<script>
export default {
data() {
return {
list: [],
page: 1,
pageSize: 20,
loading: false,
noMore: false
}
},
onLoad() {
this.loadData()
},
methods: {
async loadData() {
if (this.loading || this.noMore) return
this.loading = true
try {
const res = await this.fetchList({
page: this.page,
pageSize: this.pageSize
})
if (res.length < this.pageSize) {
this.noMore = true
}
this.list = [...this.list, ...res]
this.page++
} catch (error) {
console.error(error)
} finally {
this.loading = false
}
},
loadMore() {
this.loadData()
}
}
}
</script>
3. 图片优化
图片懒加载
<template>
<image
:src="imageSrc"
:lazy-load="true"
mode="aspectFit"
@load="handleImageLoad"
@error="handleImageError"
/>
</template>
图片压缩
// 图片压缩工具
const compressImage = (path, quality = 0.8) => {
// #ifdef APP-PLUS
return new Promise((resolve, reject) => {
plus.compressImage({
src: path,
quality: quality,
success: (result) => {
resolve(result.target)
},
fail: reject
})
})
// #endif
// #ifndef APP-PLUS
return Promise.resolve(path)
// #endif
}
// 使用
async function uploadImage(path) {
const compressedPath = await compressImage(path, 0.8)
// 上传压缩后的图片
}
使用合适的图片格式
<template>
<!-- 使用 WebP 格式(H5) -->
<!-- #ifdef H5 -->
<image src="/static/image.webp" />
<!-- #endif -->
<!-- 使用 PNG/JPG(小程序/App) -->
<!-- #ifndef H5 -->
<image src="/static/image.png" />
<!-- #endif -->
</template>
4. 网络请求优化
请求合并
// 合并多个请求
const batchRequest = async (requests) => {
return Promise.all(requests.map(req =>
uni.request({
url: req.url,
method: req.method || 'GET',
data: req.data
})
))
}
// 使用
const [userInfo, list] = await batchRequest([
{ url: '/api/user/info' },
{ url: '/api/list' }
])
请求缓存
// 请求缓存工具
const cache = new Map()
const cachedRequest = (url, options = {}) => {
const cacheKey = `${url}_${JSON.stringify(options.data || {})}`
// 检查缓存
if (cache.has(cacheKey)) {
const cached = cache.get(cacheKey)
if (Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5分钟缓存
return Promise.resolve(cached.data)
}
}
// 发起请求
return uni.request({
url,
...options
}).then(res => {
// 缓存结果
cache.set(cacheKey, {
data: res.data,
timestamp: Date.now()
})
return res.data
})
}
请求去重
// 请求去重
const pendingRequests = new Map()
const dedupeRequest = (url, options = {}) => {
const requestKey = `${url}_${JSON.stringify(options.data || {})}`
// 如果已有相同请求在进行,返回该请求的 Promise
if (pendingRequests.has(requestKey)) {
return pendingRequests.get(requestKey)
}
// 创建新请求
const requestPromise = uni.request({
url,
...options
}).finally(() => {
// 请求完成后移除
pendingRequests.delete(requestKey)
})
pendingRequests.set(requestKey, requestPromise)
return requestPromise
}
5. 代码优化
按需引入
// ❌ 不推荐:全量引入
import * as utils from '@/utils'
// ✅ 推荐:按需引入
import { formatDate, formatPrice } from '@/utils/format'
使用 Tree-shaking
// package.json
{
"sideEffects": false
}
// 使用 ES6 模块
export function util1() {}
export function util2() {}
// 按需导入
import { util1 } from './utils'
代码分割
// 路由懒加载
const routes = [
{
path: '/pages/detail/detail',
component: () => import('@/pages/detail/detail.vue')
}
]
6. 内存优化
及时清理定时器
export default {
data() {
return {
timer: null
}
},
onLoad() {
this.timer = setInterval(() => {
// 定时任务
}, 1000)
},
onUnload() {
// 清理定时器
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
}
}
清理事件监听
export default {
onLoad() {
// 监听事件
uni.onPageScroll(this.handleScroll)
},
onUnload() {
// 移除监听
uni.offPageScroll(this.handleScroll)
},
methods: {
handleScroll() {
// 滚动处理
}
}
}
避免内存泄漏
export default {
data() {
return {
largeData: null
}
},
onUnload() {
// 清理大对象
this.largeData = null
}
}
7. 启动优化
减少启动页资源
// manifest.json
{
"app-plus": {
"splashscreen": {
"autoclose": true,
"waiting": true
}
}
}
延迟加载非关键资源
export default {
onLoad() {
// 立即加载关键数据
this.loadCriticalData()
// 延迟加载非关键数据
setTimeout(() => {
this.loadNonCriticalData()
}, 1000)
},
methods: {
async loadCriticalData() {
// 关键数据
},
async loadNonCriticalData() {
// 非关键数据
}
}
}
8. 动画优化
使用 CSS 动画
<style>
/* ✅ 推荐:使用 CSS 动画 */
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* ❌ 不推荐:使用 JavaScript 动画 */
</style>
使用 transform 替代 position
<style>
/* ✅ 推荐:使用 transform */
.move {
transform: translateX(100px);
}
/* ❌ 不推荐:使用 position */
.move {
left: 100px;
}
</style>
9. 打包优化
压缩代码
// manifest.json
{
"h5": {
"optimization": {
"treeShaking": {
"enable": true
}
}
}
}
移除 console
// 生产环境移除 console
// #ifndef H5
if (process.env.NODE_ENV === 'production') {
console.log = () => {}
console.warn = () => {}
console.error = () => {}
}
// #endif
10. 性能监控
性能指标收集
// 页面加载时间
const pageLoadTime = {
start: 0,
end: 0,
start() {
this.start = Date.now()
},
end() {
this.end = Date.now()
const loadTime = this.end - this.start
console.log('页面加载时间:', loadTime + 'ms')
// 上报性能数据
this.reportPerformance(loadTime)
},
reportPerformance(loadTime) {
// 上报到服务器
uni.request({
url: '/api/performance',
method: 'POST',
data: {
loadTime,
page: getCurrentPages()[0].route
}
})
}
}
// 使用
export default {
onLoad() {
pageLoadTime.start()
},
onReady() {
pageLoadTime.end()
}
}
性能检查清单
- 使用 v-show 替代频繁切换的 v-if
- 列表使用虚拟滚动或分页加载
- 图片使用懒加载和压缩
- 网络请求使用缓存和去重
- 按需引入模块和组件
- 及时清理定时器和事件监听
- 使用 CSS 动画替代 JS 动画
- 生产环境移除 console
- 监控和上报性能指标
总结
UniApp 性能优化需要从多个方面入手:
- 渲染优化:减少不必要的渲染
- 列表优化:虚拟列表、分页加载
- 资源优化:图片压缩、懒加载
- 网络优化:请求合并、缓存、去重
- 代码优化:按需引入、代码分割
- 内存优化:及时清理资源
- 启动优化:减少启动资源
- 动画优化:使用 CSS 动画
- 打包优化:代码压缩、移除调试代码
- 性能监控:收集和分析性能数据
记住:先测量,再优化。使用性能分析工具找出瓶颈,然后有针对性地优化。
性能优化是一个持续的过程,需要在实际开发中不断实践和总结。希望这些技巧能帮助你构建更高效的 UniApp 应用!