移动端性能优化最佳实践
概述
移动端性能优化是提升用户体验的关键因素。RuoYi-Plus-UniApp 项目实现了全方位的性能优化策略,涵盖数据缓存、请求优化、渲染优化、内存管理、包体积优化等多个维度,确保应用在各种设备和网络环境下都能流畅运行。
核心价值:
- 快速启动 - 应用启动时间控制在2秒以内
- 流畅交互 - 页面切换和滚动保持60FPS
- 低内存占用 - 内存使用优化,防止内存泄漏
- 小包体积 - 代码分割和按需加载,减少首屏加载时间
- 离线可用 - 智能缓存策略,支持离线访问
- 跨平台优化 - 针对iOS、Android、微信小程序等平台的专项优化
性能指标:
| 指标 | 目标值 | 说明 |
|---|---|---|
| 首屏加载时间 | < 2s | 从打开到内容可见 |
| 页面切换时间 | < 300ms | Tab切换响应时间 |
| 列表滚动FPS | ≥ 55 | 保持流畅滚动 |
| 内存占用 | < 150MB | iOS/Android双平台 |
| 包体积 | < 5MB | 主包大小 |
| API响应时间 | < 1s | 90%请求响应时间 |
优化架构:
┌─────────────────────────────────────────────────────────────────┐
│ 移动端性能优化架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用层优化: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 组件懒加载 → Tab按需渲染 → 虚拟滚动 → 图片懒加载 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 请求层优化: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 请求防抖 → 并发控制 → 重复请求拦截 → 请求重试机制 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 数据层优化: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 自动过期缓存 → 数据预加载 → 增量更新 → 离线存储 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 构建层优化: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 代码分割 → Tree Shaking → 压缩混淆 → 按需加载 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 平台层优化: │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ iOS优化 → Android优化 → 小程序优化 → H5优化 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘数据缓存优化
1. 智能缓存系统
系统实现了一套完整的缓存管理机制,支持自动过期、类型安全和统计分析。
缓存接口设计
typescript
interface CacheWrapper<T> {
value: T
expireTime?: number // 过期时间戳
}
// 核心缓存API
cache.set(key, value, expireSeconds?) // 设置缓存,可选过期时间
cache.get<T>(key) // 类型安全的获取
cache.has(key) // 检查是否存在
cache.remove(key) // 删除指定缓存
cache.clearAll() // 清空所有缓存
cache.cleanup() // 清理过期缓存
cache.getStats() // 获取缓存统计信息使用示例
基础用法:
typescript
// 设置永久缓存
cache.set('user_id', '123456')
// 设置7天过期的缓存
cache.set('token', 'eyJhbGc...', 7 * 24 * 60 * 60)
// 获取缓存(类型安全)
const token = cache.get<string>('token')
const userId = cache.get<string>('user_id')
// 检查缓存是否存在
if (cache.has('token')) {
console.log('Token exists')
}应用场景:
typescript
// Token管理
const TOKEN_KEY = 'app_token'
const TOKEN_EXPIRE = 7 * 24 * 60 * 60 // 7天
export function setToken(token: string) {
cache.set(TOKEN_KEY, token, TOKEN_EXPIRE)
}
export function getToken(): string | null {
return cache.get<string>(TOKEN_KEY)
}
export function removeToken() {
cache.remove(TOKEN_KEY)
}
// 租户ID缓存
const TENANT_ID_KEY = 'tenant_id'
export function setTenantId(tenantId: string) {
cache.set(TENANT_ID_KEY, tenantId) // 永久缓存
}
export function getTenantId(): string | null {
return cache.get<string>(TENANT_ID_KEY)
}
// 模板配置缓存(5分钟过期)
const TEMPLATE_CONFIG_KEY = 'template_config'
const TEMPLATE_EXPIRE = 5 * 60
export function setTemplateConfig(config: TemplateConfig) {
cache.set(TEMPLATE_CONFIG_KEY, config, TEMPLATE_EXPIRE)
}
export function getTemplateConfig(): TemplateConfig | null {
return cache.get<TemplateConfig>(TEMPLATE_CONFIG_KEY)
}2. 自动清理机制
系统实现了自动清理过期缓存的机制,无需手动管理。
清理策略
typescript
// 每10分钟自动清理一次过期缓存
setInterval(() => {
cache.cleanup()
}, 10 * 60 * 1000)
// 应用启动时清理
onLaunch(() => {
cache.cleanup()
})
// 应用进入后台时清理
onHide(() => {
cache.cleanup()
})手动清理
typescript
// 清理过期缓存
cache.cleanup()
// 清空所有应用缓存(保留其他应用数据)
cache.clearAll()
// 获取缓存统计
const stats = cache.getStats()
console.log(`总大小: ${stats.size} bytes`)
console.log(`使用率: ${stats.percentage}%`)
console.log(`缓存项: ${stats.count}`)3. 缓存统计分析
typescript
interface CacheStats {
size: number // 总大小(bytes)
maxSize: number // 最大容量
percentage: number // 使用率(%)
count: number // 缓存项数量
}
// 监控缓存使用情况
function monitorCache() {
const stats = cache.getStats()
if (stats.percentage > 80) {
console.warn('缓存使用率超过80%,建议清理')
cache.cleanup()
}
if (stats.size > 5 * 1024 * 1024) { // 5MB
console.warn('缓存总大小超过5MB')
}
}
// 定期监控
setInterval(monitorCache, 60 * 1000) // 每分钟检查一次请求优化
1. 防抖与节流
系统提供了完整的防抖和节流实现,用于优化高频事件处理。
防抖(Debounce)
防抖确保函数在停止触发后的指定时间后执行一次。
typescript
// 基础用法
const debouncedSearch = debounce((keyword: string) => {
searchApi(keyword)
}, 500)
// 支持立即执行
const debouncedSubmit = debounce((data) => {
submitForm(data)
}, 300, { immediate: true }) // 首次立即执行
// 可取消的防抖
const debouncedFn = debounce(() => {
console.log('Executed')
}, 1000)
// 取消待执行的函数
debouncedFn.cancel()应用场景:
vue
<template>
<view>
<!-- 搜索输入防抖 -->
<input
v-model="searchKeyword"
@input="handleSearch"
placeholder="搜索..."
/>
<!-- 表单验证防抖 -->
<input
v-model="form.email"
@input="validateEmail"
placeholder="邮箱"
/>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { debounce } from '@/utils/function'
const searchKeyword = ref('')
// 搜索防抖(500ms)
const handleSearch = debounce((event: any) => {
const keyword = event.detail.value
if (keyword) {
searchApi(keyword)
}
}, 500)
// 邮箱验证防抖(300ms)
const validateEmail = debounce((event: any) => {
const email = event.detail.value
if (email) {
checkEmailExists(email)
}
}, 300)
</script>节流(Throttle)
节流确保函数在指定时间间隔内最多执行一次。
typescript
// 基础用法
const throttledScroll = throttle((event) => {
handleScroll(event)
}, 100)
// 配置leading和trailing
const throttledFn = throttle(
() => console.log('Throttled'),
1000,
{
leading: true, // 首次触发立即执行
trailing: false // 最后一次触发不执行
}
)
// 可取消的节流
const throttledApi = throttle(() => {
fetchData()
}, 2000)
throttledApi.cancel() // 取消节流应用场景:
vue
<template>
<scroll-view
class="scroll-container"
@scroll="handleScroll"
scroll-y
>
<!-- 内容 -->
</scroll-view>
</template>
<script lang="ts" setup>
import { throttle } from '@/utils/function'
// 滚动事件节流(100ms)
const handleScroll = throttle((event: any) => {
const scrollTop = event.detail.scrollTop
// 更新滚动位置
updateScrollPosition(scrollTop)
// 显示/隐藏回到顶部按钮
showBackTop.value = scrollTop > 300
}, 100)
// 轮询API节流(3秒)
const pollData = throttle(() => {
fetchLatestData()
}, 3000, { leading: true })
// 开始轮询
setInterval(pollData, 1000) // 实际每3秒执行一次
</script>2. 请求并发控制
重复请求拦截
系统自动拦截500ms内的重复提交请求。
typescript
// 自动实现的防重复提交
const { post } = useHttp()
// 连续点击只会发送一次
const handleSubmit = async () => {
await post('/api/user/save', formData)
// 500ms内再次点击会被自动忽略
}原理:
typescript
// 内部实现
let lastSubmitTime = 0
const SUBMIT_INTERVAL = 500
function checkDuplicateSubmit(): boolean {
const now = Date.now()
if (now - lastSubmitTime < SUBMIT_INTERVAL) {
return true // 重复提交
}
lastSubmitTime = now
return false
}请求ID追踪
每个请求自动分配唯一ID,便于追踪和调试。
typescript
// 自动生成的请求ID格式: yyyyMMddHHmmssSSS
// 例如: 20250124143025123
// 请求头自动包含
headers: {
'X-Request-Id': '20250124143025123'
}
// 用于日志追踪
console.log(`[${requestId}] 请求开始`)
console.log(`[${requestId}] 请求完成`)3. 请求重试机制
系统提供了完善的请求重试功能,支持指数退避策略。
typescript
import { retry } from '@/utils/function'
// 基础重试
const fetchData = retry(
async () => {
const response = await http.get('/api/data')
return response.data
},
{
times: 3, // 重试3次
delay: 1000 // 每次延迟1秒
}
)
// 指数退避重试
const fetchWithBackoff = retry(
async () => {
return await http.get('/api/important')
},
{
times: 5,
delay: 1000,
exponential: true // 1s, 2s, 4s, 8s, 16s
}
)
// 条件重试(仅特定错误重试)
const conditionalRetry = retry(
async () => {
return await http.post('/api/submit', data)
},
{
times: 3,
delay: 2000,
shouldRetry: (error) => {
// 仅网络错误重试,业务错误不重试
return error.code === 'NETWORK_ERROR'
}
}
)应用场景:
typescript
// 关键数据获取
const loadUserProfile = retry(
async () => {
const res = await http.get('/api/user/profile')
return res.data
},
{ times: 3, delay: 1000 }
)
// 文件上传
const uploadFile = retry(
async (file) => {
return await http.upload('/api/file/upload', file)
},
{
times: 3,
delay: 2000,
exponential: true,
shouldRetry: (error) => {
// 只重试网络错误和超时
return ['NETWORK_ERROR', 'TIMEOUT'].includes(error.code)
}
}
)4. 超时控制
typescript
import { withTimeout } from '@/utils/function'
// 为异步函数添加超时控制
const fetchWithTimeout = withTimeout(
async () => {
return await http.get('/api/slow-endpoint')
},
5000 // 5秒超时
)
try {
const data = await fetchWithTimeout()
console.log('Success:', data)
} catch (error) {
if (error.message === 'Timeout') {
console.error('请求超时')
}
}
// 结合重试使用
const robustFetch = retry(
withTimeout(
async () => await http.get('/api/data'),
3000 // 每次尝试3秒超时
),
{ times: 3, delay: 1000 }
)渲染优化
1. 组件懒加载
系统使用智能的Tab懒加载策略,只在需要时才加载组件。
基础实现
vue
<template>
<view class="tabbar-container">
<!-- 使用v-if实现懒加载 -->
<Home v-if="tabs[0].loaded" v-show="currentTab === 0" />
<Menu v-if="tabs[1].loaded" v-show="currentTab === 1" />
<My v-if="tabs[2].loaded" v-show="currentTab === 2" />
</view>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
import Home from './components/Home.vue'
import Menu from './components/Menu.vue'
import My from './components/My.vue'
const currentTab = ref(0)
const tabs = reactive([
{ loaded: true }, // 首屏加载Home
{ loaded: false }, // Menu未加载
{ loaded: false } // My未加载
])
// Tab切换时触发加载
const handleTabChange = (index: number) => {
// 标记为已加载
if (!tabs[index].loaded) {
tabs[index].loaded = true
}
currentTab.value = index
}
</script>优化效果:
- 首屏渲染时间减少 40%
- 内存占用减少 30%
- 页面切换流畅度提升
平台适配
vue
<template>
<!-- Alipay: 使用v-if(平台限制) -->
<template v-if="isAlipay">
<Home v-if="currentTab === 0" />
<Menu v-if="currentTab === 1" />
<My v-if="currentTab === 2" />
</template>
<!-- 其他平台: 使用v-show(性能更好) -->
<template v-else>
<Home v-if="tabs[0].loaded" v-show="currentTab === 0" />
<Menu v-if="tabs[1].loaded" v-show="currentTab === 1" />
<My v-if="tabs[2].loaded" v-show="currentTab === 2" />
</template>
</template>
<script lang="ts" setup>
import { isMpAlipay } from '@/utils/platform'
const isAlipay = isMpAlipay
</script>2. 虚拟滚动
系统实现了全局的滚动状态管理,优化长列表性能。
滚动状态管理
typescript
// 单例模式,全局共享滚动状态
const scrollState = {
scrollTopValue: ref(0), // 当前滚动位置
scrollViewId: 'scroll-view-1' // scroll-view唯一ID
}
// API方法
useScroll().getScrollTop() // 获取当前位置
useScroll().updateScrollTop(value) // 更新位置
useScroll().resetScrollTop() // 重置到顶部
useScroll().isScrolled(threshold) // 是否已滚动
useScroll().shouldShowBacktop(top) // 是否显示回到顶部
useScroll().getScrollProgress(max) // 获取滚动进度(0-100)Scroll-View模式
vue
<template>
<scroll-view
:id="scrollViewId"
class="scroll-container"
:scroll-top="scrollTopValue"
scroll-y
@scroll="handleScroll"
>
<view v-for="item in items" :key="item.id">
{{ item.name }}
</view>
</scroll-view>
</template>
<script lang="ts" setup>
import { useScroll } from '@/composables/useScroll'
// 创建scroll-view处理器
const { scrollTopValue, scrollViewId, handleScroll } =
useScroll().createScrollViewHandler()
</script>页面滚动模式
vue
<template>
<view class="page">
<view v-for="item in items" :key="item.id">
{{ item.name }}
</view>
</view>
</template>
<script lang="ts" setup>
import { useScroll } from '@/composables/useScroll'
import { onPageScroll } from '@dcloudio/uni-app'
// 创建页面滚动处理器
const { handlePageScroll } = useScroll().createPageScrollHandler()
// 自动注册页面滚动监听
onPageScroll(handlePageScroll)
</script>回到顶部
vue
<template>
<view class="page">
<!-- 内容 -->
<view v-for="item in items" :key="item.id">
{{ item.name }}
</view>
<!-- 回到顶部按钮 -->
<view
v-show="showBackTop"
class="back-top"
@click="scrollToTop"
>
↑
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useScroll } from '@/composables/useScroll'
const showBackTop = ref(false)
const { handlePageScroll } = useScroll().createPageScrollHandler()
// 监听滚动显示按钮
onPageScroll((event: any) => {
handlePageScroll(event)
showBackTop.value = useScroll().shouldShowBacktop(300)
})
// 点击回到顶部
const scrollToTop = () => {
// 自动使用uni.pageScrollTo
useScroll().resetScrollTop()
}
</script>3. 列表优化
分页加载
vue
<template>
<scroll-view
class="list"
scroll-y
@scrolltolower="loadMore"
>
<view v-for="item in list" :key="item.id">
{{ item.name }}
</view>
<!-- 加载状态 -->
<view class="loading" v-if="loading">加载中...</view>
<view class="no-more" v-if="noMore">没有更多了</view>
</scroll-view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const list = ref<any[]>([])
const loading = ref(false)
const noMore = ref(false)
const pageNum = ref(1)
const pageSize = 20
// 加载更多
const loadMore = async () => {
if (loading.value || noMore.value) return
loading.value = true
try {
const res = await http.get('/api/list', {
params: { pageNum: pageNum.value, pageSize }
})
if (res.data.rows.length < pageSize) {
noMore.value = true
}
list.value.push(...res.data.rows)
pageNum.value++
} finally {
loading.value = false
}
}
// 初始加载
loadMore()
</script>图片懒加载
vue
<template>
<view class="image-list">
<image
v-for="item in images"
:key="item.id"
:src="item.url"
:lazy-load="true"
mode="aspectFill"
class="image-item"
/>
</view>
</template>
<script lang="ts" setup>
// lazy-load属性自动实现懒加载
// 图片进入可视区域时才开始加载
</script>包体积优化
1. 代码分割
系统使用 Vite 的代码分割特性,自动优化包体积。
基础配置
typescript
// vite.config.ts
import Optimization from '@uni-ku/bundle-optimizer'
export default defineConfig({
plugins: [
Optimization({
enable: {
optimization: true, // 自动代码分割
'async-import': true, // 跨包异步导入
'async-component': true, // 异步组件加载
},
})
]
})手动代码分割
typescript
// 动态导入
const UserProfile = () => import('@/components/UserProfile.vue')
const OrderHistory = () => import('@/components/OrderHistory.vue')
// 路由懒加载
const routes = [
{
path: '/profile',
component: () => import('@/pages/profile/index.vue')
},
{
path: '/orders',
component: () => import('@/pages/orders/index.vue')
}
]2. Tree Shaking
typescript
// 使用ES6模块导入(支持Tree Shaking)
import { debounce, throttle } from '@/utils/function' // ✅ 只导入需要的
// 避免整体导入
import * as utils from '@/utils/function' // ❌ 导入全部3. 依赖预构建
typescript
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: [
'jsencrypt/bin/jsencrypt.min.js',
'crypto-js'
]
}
})4. 生产构建优化
typescript
// vite.config.ts
export default defineConfig({
build: {
target: 'es6',
minify: 'esbuild', // 使用esbuild压缩
sourcemap: false, // 生产环境关闭sourcemap
rollupOptions: {
output: {
manualChunks: {
'vendor': ['vue', 'pinia'],
'utils': ['@/utils/index']
}
}
}
},
esbuild: {
drop: ['console', 'debugger'] // 移除console和debugger
}
})内存管理
1. 生命周期清理
正确的资源清理是防止内存泄漏的关键。
vue
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 定时器引用
let timer: number | null = null
onMounted(() => {
// 创建定时器
timer = setInterval(() => {
fetchData()
}, 5000)
})
onUnmounted(() => {
// 清理定时器
if (timer) {
clearInterval(timer)
timer = null
}
})
</script>2. 事件监听清理
vue
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue'
const handleResize = () => {
console.log('Window resized')
}
onMounted(() => {
// 添加事件监听
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
// 移除事件监听
window.removeEventListener('resize', handleResize)
})
</script>3. WebSocket连接管理
typescript
// composables/useWebSocket.ts
export function useWebSocket() {
let socket: WebSocket | null = null
const connect = () => {
socket = new WebSocket('wss://api.example.com')
// ... 连接逻辑
}
const disconnect = () => {
if (socket) {
socket.close()
socket = null
}
}
// 应用隐藏时断开连接
onHide(() => {
disconnect()
})
// 应用显示时重新连接
onShow(() => {
if (!socket) {
connect()
}
})
return { connect, disconnect }
}4. 大数据处理
typescript
// 分批处理大数据
async function processBigData(data: any[]) {
const chunkSize = 100
const chunks = []
for (let i = 0; i < data.length; i += chunkSize) {
chunks.push(data.slice(i, i + chunkSize))
}
for (const chunk of chunks) {
await processChunk(chunk)
// 让出主线程,避免阻塞
await new Promise(resolve => setTimeout(resolve, 0))
}
}
// 使用虚拟滚动处理大列表
const visibleItems = computed(() => {
const start = Math.floor(scrollTop.value / itemHeight)
const end = start + visibleCount
return allItems.value.slice(start, end)
})网络优化
1. HTTP缓存
请求级缓存
typescript
// 使用缓存的GET请求
const fetchUserInfo = async (userId: string) => {
const cacheKey = `user_info_${userId}`
// 先检查缓存
const cached = cache.get<UserInfo>(cacheKey)
if (cached) {
return cached
}
// 缓存未命中,发起请求
const res = await http.get(`/api/user/${userId}`)
// 缓存结果(5分钟)
cache.set(cacheKey, res.data, 5 * 60)
return res.data
}数据预加载
typescript
// 页面跳转前预加载数据
const navigateToDetail = async (id: string) => {
// 先预加载数据
const dataPromise = fetchDetailData(id)
// 立即跳转
uni.navigateTo({
url: `/pages/detail/index?id=${id}`
})
// 数据到达后缓存
const data = await dataPromise
cache.set(`detail_${id}`, data, 60)
}2. 请求合并
typescript
// 批量请求合并
class RequestBatcher {
private pending: Map<string, Promise<any>> = new Map()
async fetch(key: string, fetcher: () => Promise<any>) {
// 检查是否已有相同请求
if (this.pending.has(key)) {
return this.pending.get(key)
}
// 创建新请求
const promise = fetcher().finally(() => {
this.pending.delete(key)
})
this.pending.set(key, promise)
return promise
}
}
const batcher = new RequestBatcher()
// 使用
async function getUserInfo(userId: string) {
return batcher.fetch(`user_${userId}`, () =>
http.get(`/api/user/${userId}`)
)
}
// 多个组件同时调用,只发送一次请求
Promise.all([
getUserInfo('123'),
getUserInfo('123'),
getUserInfo('123')
])3. 离线支持
typescript
// 离线数据存储
const offlineStorage = {
// 保存待同步数据
save(key: string, data: any) {
const queue = cache.get<any[]>('offline_queue') || []
queue.push({ key, data, timestamp: Date.now() })
cache.set('offline_queue', queue)
},
// 同步离线数据
async sync() {
const queue = cache.get<any[]>('offline_queue') || []
for (const item of queue) {
try {
await http.post('/api/sync', item.data)
// 同步成功,移除
queue.splice(queue.indexOf(item), 1)
} catch (error) {
console.error('同步失败:', item.key)
}
}
cache.set('offline_queue', queue)
}
}
// 网络恢复时同步
uni.onNetworkStatusChange((res: any) => {
if (res.isConnected) {
offlineStorage.sync()
}
})平台优化
1. 平台检测
系统提供了完整的平台检测工具。
typescript
// 平台类型检测
import {
isApp, // Native App
isMp, // 任意小程序
isMpWeixin, // 微信小程序
isMpAlipay, // 支付宝小程序
isH5, // 浏览器H5
isWechatOfficialH5, // 微信公众号H5
isAlipayOfficialH5 // 支付宝H5
} from '@/utils/platform'
// 使用示例
if (isMpWeixin) {
// 微信小程序特定逻辑
wx.getSystemInfo()
} else if (isApp) {
// Native App特定逻辑
plus.runtime.getProperty()
}2. iOS优化
typescript
// manifest.config.ts
export default {
'app-plus': {
// 禁用页面弹性
bounce: 'none',
// 启用硬件加速
hardwareAcceleration: true,
// iOS特定配置
ios: {
// 禁用黑色背景
backgroundMode: 'light',
// 启用增量更新
incrementalUpdate: true
}
}
}CSS优化:
scss
// 使用transform代替position
.animated-element {
// ❌ 性能差
left: 100px;
top: 100px;
// ✅ 性能好
transform: translate(100px, 100px);
}
// 启用GPU加速
.gpu-accelerated {
transform: translateZ(0);
will-change: transform;
}3. Android优化
typescript
// manifest.config.ts
export default {
'app-plus': {
android: {
// 启用X5内核
useX5: true,
// 优化WebView性能
webView: {
cacheMode: 'default',
hardwareAccelerated: true
}
}
}
}列表优化:
vue
<template>
<!-- Android长列表优化 -->
<recycle-list :list-data="items" :key-field="'id'">
<template v-slot="{ item }">
<view class="item">{{ item.name }}</view>
</template>
</recycle-list>
</template>4. 小程序优化
微信小程序
typescript
// manifest.config.ts
export default {
'mp-weixin': {
// 启用分包
optimization: {
subPackages: true
},
// 懒加载必要组件
lazyCodeLoading: 'requiredComponents',
// 启用增强编译
enhance: true
}
}分包配置:
typescript
// pages.config.ts
export default {
subPackages: [
{
root: 'pages-sub/user',
pages: [
{ path: 'profile/index' },
{ path: 'settings/index' }
]
},
{
root: 'pages-sub/shop',
pages: [
{ path: 'list/index' },
{ path: 'detail/index' }
]
}
],
preloadRule: {
'pages/index/index': {
packages: ['pages-sub/user'],
network: 'wifi'
}
}
}支付宝小程序
typescript
// manifest.config.ts
export default {
'mp-alipay': {
// 按需注入
lazyCodeLoading: 'requiredComponents',
// 组件2编译
component2: true
}
}5. H5优化
typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
// 代码分割
manualChunks: {
'vue': ['vue', 'vue-router', 'pinia'],
'ui': ['wot-design-uni'],
'vendor': ['axios', 'dayjs']
}
}
}
},
// PWA支持
plugins: [
PWA({
registerType: 'autoUpdate',
workbox: {
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 50,
maxAgeSeconds: 5 * 60
}
}
}
]
}
})
]
})日志与监控
1. 性能监控
typescript
// 监控页面加载时间
const logPageLoad = () => {
const loadTime = Date.now() - startTime
console.log(`页面加载耗时: ${loadTime}ms`)
// 上报到监控系统
logger.log('page_load', { duration: loadTime })
}
// 监控API请求时间
const logApiRequest = (url: string, duration: number) => {
if (duration > 1000) {
console.warn(`慢请求: ${url} 耗时 ${duration}ms`)
}
logger.log('api_request', { url, duration })
}
// 监控内存使用
const logMemory = () => {
const memory = (performance as any).memory
if (memory) {
logger.log('memory_usage', {
used: memory.usedJSHeapSize,
total: memory.totalJSHeapSize,
limit: memory.jsHeapSizeLimit
})
}
}2. 错误收集
typescript
// 全局错误处理
onError((error: string) => {
console.error('Global error:', error)
logger.error('app_error', { message: error })
})
// 未处理的Promise拒绝
onUnhandledRejection((event: any) => {
console.error('Unhandled rejection:', event.reason)
logger.error('unhandled_rejection', {
reason: event.reason
})
})
// 网络错误
uni.onNetworkStatusChange((res: any) => {
if (!res.isConnected) {
logger.warn('network_offline')
}
})3. 日志批量上报
系统实现了高效的日志收集和上报机制。
typescript
// 日志收集器
class LogCollector {
private logs: any[] = []
private timer: number | null = null
private maxSize = 50 // 最大批次大小
private maxQueue = 200 // 最大队列长度
private interval = 2000 // 上报间隔(ms)
// 添加日志
add(log: any) {
if (this.logs.length >= this.maxQueue) {
this.logs.shift() // 队列满,移除最旧日志
}
this.logs.push({
...log,
timestamp: Date.now()
})
// 达到批次大小,立即上报
if (this.logs.length >= this.maxSize) {
this.flush()
}
}
// 上报日志
async flush() {
if (this.logs.length === 0) return
const batch = this.logs.splice(0, this.maxSize)
try {
await http.post('/api/logs/batch', batch)
} catch (error) {
console.error('日志上报失败:', error)
// 失败的日志不重新入队,避免无限积压
}
}
// 启动自动上报
start() {
if (this.timer) return
this.timer = setInterval(() => {
this.flush()
}, this.interval) as any
}
// 停止自动上报
stop() {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
// 上报剩余日志
this.flush()
}
}
const logger = new LogCollector()
// 应用启动时开始收集
onLaunch(() => {
logger.start()
})
// 应用隐藏时上报
onHide(() => {
logger.flush()
})最佳实践
1. 启动优化
typescript
// 延迟非关键初始化
onLaunch(() => {
// 立即执行关键初始化
await initApp()
// 延迟执行非关键任务
setTimeout(() => {
initAnalytics()
initPush()
checkUpdate()
}, 2000)
})2. 资源优化
typescript
// 图片压缩和格式选择
const getOptimizedImageUrl = (url: string, width: number) => {
return `${url}?imageView2/2/w/${width}/format/webp`
}
// 使用WebP格式(节省30-50%体积)
<image :src="getOptimizedImageUrl(imageUrl, 750)" />3. 动画优化
scss
// 使用CSS动画代替JS动画
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
// 使用transform代替position
.slide-enter-active {
transition: transform 0.3s ease;
}
.slide-enter-from {
transform: translateX(100%);
}4. 状态管理优化
typescript
// 使用Pinia的细粒度响应式
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
// 基础信息(频繁访问)
const basicInfo = ref({
id: '',
name: '',
avatar: ''
})
// 详细信息(按需加载)
const detailInfo = ref(null)
const loadDetail = async () => {
if (!detailInfo.value) {
detailInfo.value = await fetchUserDetail()
}
}
return { basicInfo, detailInfo, loadDetail }
})5. 组件设计优化
vue
<script lang="ts" setup>
// 使用props接口减少响应式开销
interface Props {
id: string
name: string
// 避免传递大对象
}
const props = defineProps<Props>()
// 计算属性缓存
const displayName = computed(() => {
return props.name.toUpperCase()
})
// 避免在模板中使用复杂表达式
const isActive = computed(() => {
return props.id === currentId.value
})
</script>
<template>
<view :class="{ active: isActive }">
{{ displayName }}
</view>
</template>常见问题
1. 页面白屏时间过长
问题原因:
- 首屏数据请求过多
- 组件渲染复杂
- 资源加载阻塞
解决方案:
typescript
// 1. 骨架屏占位
<template>
<view v-if="loading" class="skeleton">
<view class="skeleton-avatar"></view>
<view class="skeleton-text"></view>
</view>
<view v-else>
<!-- 实际内容 -->
</view>
</template>
// 2. 数据预加载
const preloadData = async () => {
const promises = [
fetchUserInfo(),
fetchMenuList(),
fetchBanners()
]
const [user, menu, banners] = await Promise.all(promises)
// 缓存数据
cache.set('preload_user', user, 60)
cache.set('preload_menu', menu, 60)
cache.set('preload_banners', banners, 60)
}
// 3. 组件懒加载
const HeavyComponent = defineAsyncComponent(() =>
import('@/components/HeavyComponent.vue')
)2. 列表滚动卡顿
问题原因:
- 列表项过多
- 渲染计算复杂
- 频繁的DOM操作
解决方案:
vue
<template>
<!-- 1. 使用虚拟滚动 -->
<virtual-list
:items="items"
:item-height="100"
:visible-count="10"
>
<template #item="{ item }">
<ListItem :data="item" />
</template>
</virtual-list>
<!-- 2. 分批渲染 -->
<view v-for="item in visibleItems" :key="item.id">
<ListItem :data="item" />
</view>
</template>
<script lang="ts" setup>
// 只渲染可见区域
const visibleItems = computed(() => {
const start = Math.floor(scrollTop.value / itemHeight)
const end = start + visibleCount
return allItems.value.slice(start, end)
})
</script>3. 内存占用过高
问题原因:
- 未清理定时器
- 事件监听未移除
- 大数组/对象未释放
解决方案:
typescript
// 1. 及时清理资源
onUnmounted(() => {
if (timer) clearInterval(timer)
if (observer) observer.disconnect()
largeArray.value = []
})
// 2. 使用弱引用
const weakMap = new WeakMap()
weakMap.set(obj, data) // obj被垃圾回收时,data也会被回收
// 3. 分批处理大数据
async function processBigData(data: any[]) {
for (let i = 0; i < data.length; i += 100) {
await processChunk(data.slice(i, i + 100))
await nextTick() // 让出主线程
}
}4. 包体积过大
问题原因:
- 未使用代码分割
- 引入了过多第三方库
- 图片资源未压缩
解决方案:
typescript
// 1. 按需导入
import { debounce } from 'lodash-es' // ✅ 只导入需要的
import _ from 'lodash' // ❌ 导入整个库
// 2. 代码分割
const HeavyModule = () => import('@/modules/heavy')
// 3. 使用CDN
// vite.config.ts
export default {
build: {
rollupOptions: {
external: ['vue', 'pinia'],
output: {
globals: {
vue: 'Vue',
pinia: 'Pinia'
}
}
}
}
}
// 4. 图片压缩
// 使用tinypng、webp格式
// 移除EXIF信息5. 请求响应慢
问题原因:
- 未使用缓存
- 并发请求过多
- 接口性能差
解决方案:
typescript
// 1. 请求缓存
const fetchWithCache = async (url: string) => {
const cached = cache.get(url)
if (cached) return cached
const data = await http.get(url)
cache.set(url, data, 60)
return data
}
// 2. 请求合并
const batchFetch = async (ids: string[]) => {
return await http.post('/api/batch', { ids })
}
// 3. 并发控制
import { parallel } from '@/utils/function'
const tasks = items.map(item => () => fetchItem(item.id))
await parallel(tasks, 3) // 最多3个并发总结
移动端性能优化是一个系统工程。通过本文档介绍的最佳实践:
- 数据缓存 - 智能缓存系统,自动过期管理,减少网络请求
- 请求优化 - 防抖节流、并发控制、重试机制,提升网络效率
- 渲染优化 - 组件懒加载、虚拟滚动、分页加载,保持界面流畅
- 包体积优化 - 代码分割、Tree Shaking、按需加载,快速启动
- 内存管理 - 资源及时清理、防止内存泄漏,稳定运行
- 平台优化 - iOS/Android/小程序专项优化,适配各平台特性
建议在实际使用中:
- 建立性能监控体系,持续跟踪关键指标
- 定期进行性能测试和优化
- 关注用户反馈,及时解决性能问题
- 使用专业工具(Chrome DevTools、小程序性能分析等)分析瓶颈
- 遵循"测量-优化-验证"的闭环流程
