uni-app性能优化指南:从加载到渲染的全方位提升 0 次阅读

性能优化是提升用户体验的关键因素,本文将从多个维度详细介绍uni-app应用的性能优化策略,帮助你构建流畅的跨端应用。

1. 首屏加载优化

1.1 分包加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// pages.json
{
"pages": [{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
}],
"subPackages": [{
"root": "pagesA",
"pages": [{
"path": "list/index",
"style": {
"navigationBarTitleText": "列表页"
}
}]
}]
}

// 预加载分包
onLoad() {
// 进入首页时预加载分包
uni.preloadSubPackage({
name: 'pagesA',
success: () => {
console.log('分包预加载成功')
},
fail: () => {
console.log('分包预加载失败')
}
})
}

1.2 图片优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template>
<view class="container">
<!-- 使用懒加载 -->
<image
v-for="(item, index) in images"
:key="index"
:src="item.url"
:lazy-load="true"
mode="aspectFill"
/>

<!-- 使用webp格式 -->
<image
:src="getWebpUrl(image.url)"
mode="aspectFill"
/>
</view>
</template>

<script>
export default {
methods: {
// 判断是否支持webp
checkWebp() {
try {
return document.createElement('canvas')
.toDataURL('image/webp')
.indexOf('data:image/webp') === 0
} catch (e) {
return false
}
},

// 获取webp图片地址
getWebpUrl(url) {
if (this.checkWebp()) {
return url.replace(/\.(jpg|png)$/, '.webp')
}
return url
}
}
}
</script>

1.3 骨架屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<!-- components/skeleton.vue -->
<template>
<view class="skeleton" v-if="loading">
<view class="skeleton-header">
<view class="skeleton-avatar"></view>
<view class="skeleton-content">
<view class="skeleton-title"></view>
<view class="skeleton-desc"></view>
</view>
</view>
<view class="skeleton-list">
<view
class="skeleton-item"
v-for="i in 5"
:key="i"
></view>
</view>
</view>
</template>

<style lang="scss">
.skeleton {
padding: 20rpx;

&-header {
display: flex;
margin-bottom: 30rpx;
}

&-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #f0f0f0;
margin-right: 20rpx;
}

&-content {
flex: 1;
}

&-title {
width: 60%;
height: 40rpx;
background: #f0f0f0;
margin-bottom: 20rpx;
}

&-desc {
width: 80%;
height: 32rpx;
background: #f0f0f0;
}

&-item {
height: 160rpx;
background: #f0f0f0;
margin-bottom: 20rpx;
}

&-avatar,
&-title,
&-desc,
&-item {
animation: skeleton-loading 1.4s ease infinite;
}
}

@keyframes skeleton-loading {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
</style>

2. 渲染性能优化

2.1 长列表优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template>
<view class="list-container">
<!-- 虚拟列表 -->
<recycle-list
:list="list"
:height="listHeight"
:item-height="itemHeight"
:buffer-scale="1"
>
<template v-slot="{ item }">
<view class="list-item">
{{ item.title }}
</view>
</template>
</recycle-list>
</view>
</template>

<script>
import RecycleList from '@/components/recycle-list'

export default {
components: {
RecycleList
},
data() {
return {
list: [],
listHeight: 600,
itemHeight: 100
}
},
methods: {
// 分页加载数据
async loadData(page = 1) {
const { list } = await this.$api.getList({ page })
this.list = [...this.list, ...list]
}
}
}
</script>

2.2 避免频繁更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<script>
export default {
data() {
return {
timer: null,
count: 0
}
},
methods: {
// 使用节流
throttleUpdate() {
if (this.timer) return
this.timer = setTimeout(() => {
this.count++
this.timer = null
}, 16)
},

// 使用requestAnimationFrame
rafUpdate() {
requestAnimationFrame(() => {
this.count++
})
}
},
beforeDestroy() {
// 清理定时器
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
}
}
</script>

3. 内存优化

3.1 及时清理资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
export default {
data() {
return {
observer: null,
listeners: []
}
},
methods: {
initObserver() {
this.observer = new IntersectionObserver(entries => {
// 处理逻辑
})
},
addListener(element, event, handler) {
element.addEventListener(event, handler)
this.listeners.push({ element, event, handler })
}
},
beforeDestroy() {
// 清理观察者
if (this.observer) {
this.observer.disconnect()
this.observer = null
}

// 移除事件监听
this.listeners.forEach(({ element, event, handler }) => {
element.removeEventListener(event, handler)
})
this.listeners = []
}
}

3.2 缓存管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// utils/cache.js
class Cache {
constructor(options = {}) {
this.storage = options.storage || uni.getStorageSync
this.prefix = options.prefix || 'cache_'
this.expire = options.expire || 0
}

// 设置缓存
set(key, value, expire = 0) {
const data = {
value,
expire: expire ? Date.now() + expire * 1000 : 0
}
this.storage(this.prefix + key, JSON.stringify(data))
}

// 获取缓存
get(key) {
const data = this.storage(this.prefix + key)
if (!data) return null

try {
const { value, expire } = JSON.parse(data)
// 判断是否过期
if (expire && expire < Date.now()) {
this.remove(key)
return null
}
return value
} catch (e) {
return null
}
}

// 移除缓存
remove(key) {
this.storage(this.prefix + key, '')
}

// 清空缓存
clear() {
const keys = uni.getStorageInfoSync().keys
keys.forEach(key => {
if (key.startsWith(this.prefix)) {
this.remove(key.slice(this.prefix.length))
}
})
}
}

export default new Cache()

4. 网络优化

4.1 请求优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// utils/request.js
class Request {
constructor() {
this.queue = new Map() // 请求队列
this.cache = new Map() // 请求缓存
}

// 发送请求
async request(options) {
const key = this.getRequestKey(options)

// 判断是否有相同请求在队列中
if (this.queue.has(key)) {
return this.queue.get(key)
}

// 判断是否有缓存
if (options.useCache) {
const cached = this.cache.get(key)
if (cached && cached.expire > Date.now()) {
return cached.data
}
}

// 发送请求
const promise = new Promise((resolve, reject) => {
uni.request({
...options,
success: res => {
// 缓存响应
if (options.useCache) {
this.cache.set(key, {
data: res.data,
expire: Date.now() + (options.cacheTime || 60000)
})
}
resolve(res.data)
},
fail: reject,
complete: () => {
// 请求完成后从队列移除
this.queue.delete(key)
}
})
})

// 添加到队列
this.queue.set(key, promise)
return promise
}

// 生成请求的唯一标识
getRequestKey(options) {
return `${options.url}:${JSON.stringify(options.data)}`
}
}

export default new Request()

4.2 离线缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
// utils/offline.js
class OfflineStorage {
constructor() {
this.version = '1.0.0'
this.table = 'offline_data'
}

// 初始化数据库
async init() {
return new Promise((resolve, reject) => {
plus.sqlite.openDatabase({
name: 'app.db',
path: '_doc/app.db',
success: () => {
this.createTable()
resolve()
},
fail: reject
})
})
}

// 创建表
createTable() {
const sql = `
CREATE TABLE IF NOT EXISTS ${this.table} (
id TEXT PRIMARY KEY,
data TEXT,
expire INTEGER,
version TEXT
)
`
plus.sqlite.executeSql({
name: 'app.db',
sql
})
}

// 保存数据
save(key, data, expire = 0) {
const sql = `
REPLACE INTO ${this.table} (id, data, expire, version)
VALUES (?, ?, ?, ?)
`
const values = [
key,
JSON.stringify(data),
expire ? Date.now() + expire * 1000 : 0,
this.version
]

plus.sqlite.executeSql({
name: 'app.db',
sql,
values
})
}

// 获取数据
async get(key) {
return new Promise((resolve, reject) => {
const sql = `
SELECT * FROM ${this.table}
WHERE id = ? AND version = ?
`
plus.sqlite.selectSql({
name: 'app.db',
sql,
values: [key, this.version],
success: res => {
if (!res.length) {
resolve(null)
return
}

const { data, expire } = res[0]
// 判断是否过期
if (expire && expire < Date.now()) {
this.remove(key)
resolve(null)
return
}

resolve(JSON.parse(data))
},
fail: reject
})
})
}

// 移除数据
remove(key) {
const sql = `
DELETE FROM ${this.table}
WHERE id = ?
`
plus.sqlite.executeSql({
name: 'app.db',
sql,
values: [key]
})
}

// 清空数据
clear() {
const sql = `
DELETE FROM ${this.table}
WHERE version = ?
`
plus.sqlite.executeSql({
name: 'app.db',
sql,
values: [this.version]
})
}
}

export default new OfflineStorage()

5. 最佳实践建议

  1. 合理使用分包加载
  2. 优化图片加载策略
  3. 实现虚拟列表
  4. 避免频繁更新
  5. 及时清理资源
  6. 做好缓存管理
  7. 优化网络请求
  8. 实现离线缓存

6. 性能监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// utils/performance.js
class Performance {
constructor() {
this.data = {
// 页面性能
page: {
ready: 0,
load: 0
},
// 接口性能
api: new Map(),
// 资源性能
resource: new Map()
}
}

// 记录页面性能
recordPage(type, time) {
this.data.page[type] = time
}

// 记录接口性能
recordApi(url, time) {
const api = this.data.api.get(url) || {
count: 0,
total: 0,
max: 0,
min: Infinity
}

api.count++
api.total += time
api.max = Math.max(api.max, time)
api.min = Math.min(api.min, time)

this.data.api.set(url, api)
}

// 记录资源性能
recordResource(url, time) {
this.data.resource.set(url, time)
}

// 获取性能数据
getData() {
const { page, api, resource } = this.data
return {
page,
api: Array.from(api.entries()).map(([url, data]) => ({
url,
...data,
avg: data.total / data.count
})),
resource: Array.from(resource.entries()).map(([url, time]) => ({
url,
time
}))
}
}

// 上报性能数据
report() {
const data = this.getData()
// 上报逻辑
console.log('性能数据:', data)
}
}

export default new Performance()

7. 总结

  1. 优化首屏加载
  2. 提升渲染性能
  3. 管理内存资源
  4. 优化网络请求
  5. 实现性能监控

如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力!

上一篇 uni-app安全防护指南:构建可靠的跨端应用
下一篇 uni-app动画效果实战:从基础到高级的动画实现指南
感谢您的支持!
微信赞赏码 微信赞赏
支付宝赞赏码 支付宝赞赏