在移动互联网时代,用户对应用性能的要求越来越高。据统计,如果一个应用的启动时间超过3秒,将有53%的用户选择放弃使用。对于房产行业的移动应用来说,性能优化更是至关重要,因为它直接影响到用户的看房体验和决策效率。
房产应用相比其他类型的应用,面临着更多的性能挑战:
良好的性能优化不仅能提升用户体验,还能带来实际的业务价值:
本文将从启动性能、网络优化、渲染性能、内存管理、缓存策略和性能监控六个方面,为大家详细介绍uni-app房产应用的性能优化实践。
应用启动性能是用户的第一印象,也是最关键的性能指标之一。房产应用通常包含大量的功能模块和第三方依赖,如何在保证功能完整性的同时实现快速启动,是我们面临的首要挑战。
优化背景
传统的应用启动方式会在启动时加载所有模块,这导致了不必要的资源消耗和启动时间延长。对于房产应用这种功能复杂的应用,我们需要采用更智能的加载策略。
核心思路
实施要点
// main.js - uni-app启动优化
import Vue from 'vue'
import App from './App'
import store from './store'
Vue.config.productionTip = false
App.mpType = 'app'
// 按需加载的模块
const loadModules = async () => {
try {
const modules = await Promise.all([
import('uview-ui'),
// 其他非核心模块可以在这里动态加载
])
// 注册uView UI
Vue.use(modules[0].default)
return modules
} catch (error) {
console.error('模块加载失败:', error)
// 降级处理,使用基础功能
return []
}
}
// 预加载关键资源
const preloadResources = () => {
// 预加载字体 - 仅在H5平台
// #ifdef H5
uni.loadFontFace({
family: 'PingFang SC',
source: 'url("/static/fonts/PingFangSC.woff2")',
success: () => {
console.log('字体加载成功')
},
fail: (err) => {
console.warn('字体加载失败:', err)
}
})
// 预加载关键图片
const criticalImages = [
'/static/images/logo.png',
'/static/images/house-default.png',
'/static/images/user-avatar-default.png'
]
criticalImages.forEach(src => {
const img = new Image()
img.src = src
})
// #endif
// 预加载关键数据
preloadCriticalData()
}
// 预加载关键数据
const preloadCriticalData = async () => {
try {
// 预加载用户信息
const userInfo = uni.getStorageSync('userInfo')
if (userInfo) {
store.commit('user/SET_USER_INFO', userInfo)
}
// 预加载应用配置
const config = uni.getStorageSync('appConfig')
if (config) {
store.commit('app/SET_CONFIG', config)
}
} catch (error) {
console.error('预加载数据失败:', error)
}
}
// 应用初始化
const initApp = async () => {
try {
// 显示启动屏 - 延迟显示避免uni未初始化
setTimeout(() => {
uni.showLoading({
title: '正在启动...',
mask: true
})
}, 100)
// 并行加载模块和数据
await Promise.all([
loadModules(),
preloadResources()
])
// 创建应用实例
const app = new Vue({
store,
...App
})
// 挂载应用
app.$mount()
// 隐藏启动屏
uni.hideLoading()
} catch (error) {
console.error('应用启动失败:', error)
uni.hideLoading()
uni.showModal({
title: '启动失败',
content: '应用启动失败,请重试',
showCancel: false,
success: () => {
// 重启应用
setTimeout(() => {
initApp()
}, 1000)
}
})
}
}
// 启动应用
initApp()
优化必要性
房产应用由于功能复杂,往往包含大量的页面、组件和资源文件,如果不进行合理的分包,很容易超出平台限制。特别是微信小程序有2MB的包体积限制,这要求我们必须采用精细的分包策略。
分包策略
关键配置说明
pages.json
中的分包配置是核心,需要合理规划页面分布preloadRule
可以在wifi环境下预加载分包,提升用户体验// pages.json - 分包配置
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": false
}
},
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
}
}
],
"subPackages": [
{
"root": "house-package",
"pages": [
{
"path": "list/index",
"style": {
"navigationBarTitleText": "房源列表",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
},
{
"path": "detail/index",
"style": {
"navigationBarTitleText": "房源详情"
}
}
]
},
{
"root": "user-package",
"pages": [
{
"path": "profile/index",
"style": {
"navigationBarTitleText": "个人中心"
}
},
{
"path": "settings/index",
"style": {
"navigationBarTitleText": "设置"
}
}
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "wifi",
"packages": ["house-package"]
}
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "房产助手",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
// vue.config.js - 构建优化
const path = require('path')
module.exports = {
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 第三方库
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
reuseExistingChunk: true
},
// 公共组件
components: {
test: /[\\/]components[\\/]/,
name: 'components',
chunks: 'all',
priority: 5,
minChunks: 2
},
// 工具函数
utils: {
test: /[\\/]utils[\\/]/,
name: 'utils',
chunks: 'all',
priority: 3,
minChunks: 2
}
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
// 使用压缩版本
'lodash': 'lodash-es'
}
}
},
chainWebpack: config => {
// 图片压缩
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
mozjpeg: { progressive: true, quality: 80 },
optipng: { enabled: false },
pngquant: { quality: [0.65, 0.8], speed: 4 },
gifsicle: { interlaced: false },
webp: { quality: 75 }
})
.end()
}
}
// babel.config.js - 按需引入配置
module.exports = {
presets: [
['@vue/cli-plugin-babel/preset', {
useBuiltIns: 'usage',
corejs: 3
}]
],
plugins: [
[
'import',
{
libraryName: 'uview-ui',
libraryDirectory: 'components',
style: false
},
'uview-ui'
]
]
}
网络性能是影响用户体验的关键因素,特别是在移动网络环境下。房产应用需要频繁地获取房源数据、用户信息、地图数据等,如何优化网络请求,减少等待时间,提高数据加载效率,是我们必须解决的核心问题。
策略概述
请求优化的核心在于减少不必要的网络请求,提高请求成功率,并通过缓存和预加载等技术手段提升用户体验。我们需要从请求去重、智能缓存、离线存储、错误重试等多个维度进行优化。
优化原则
实现要点
// utils/request-optimizer.js
class RequestOptimizer {
constructor() {
this.cache = new Map()
this.pending = new Map()
this.retryQueue = []
this.maxRetries = 3
this.retryDelay = 1000
}
// 生成请求键
getRequestKey(config) {
const { url, method = 'GET', params = {}, data = {} } = config
return `${method}:${url}:${JSON.stringify(params)}:${JSON.stringify(data)}`
}
// 生成缓存键
getCacheKey(config) {
return this.getRequestKey(config)
}
// 发起请求
makeRequest(config) {
return new Promise((resolve, reject) => {
const startTime = Date.now()
uni.request({
...config,
success: (res) => {
const responseTime = Date.now() - startTime
// 记录响应时间
this.recordResponseTime(config.url, responseTime)
if (res.statusCode === 200) {
resolve(res.data)
} else {
reject(new Error(`HTTP ${res.statusCode}: ${res.data?.message || 'Request failed'}`))
}
},
fail: (error) => {
reject(error)
}
})
})
}
// 记录响应时间
recordResponseTime(url, responseTime) {
// 可以发送到性能监控服务
console.log(`API响应时间 ${url}: ${responseTime}ms`)
}
// 检查缓存是否有效
isCacheValid(cached, config) {
if (!cached) return false
const { timestamp, ttl } = cached
if (!ttl) return true
return Date.now() - timestamp < ttl
}
// 请求去重
async deduplicate(config) {
const key = this.getRequestKey(config)
// 如果已有相同请求在进行中
if (this.pending.has(key)) {
return this.pending.get(key)
}
// 创建新请求
const promise = this.makeRequestWithRetry(config)
this.pending.set(key, promise)
// 请求完成后清理
promise.finally(() => {
this.pending.delete(key)
})
return promise
}
// 带重试的请求
async makeRequestWithRetry(config, retryCount = 0) {
try {
return await this.makeRequest(config)
} catch (error) {
if (retryCount < this.maxRetries) {
// 延迟重试
await new Promise(resolve => setTimeout(resolve, this.retryDelay * (retryCount + 1)))
return this.makeRequestWithRetry(config, retryCount + 1)
}
throw error
}
}
// 智能缓存
async smartCache(config) {
const cacheKey = this.getCacheKey(config)
const cached = this.cache.get(cacheKey)
if (cached && this.isCacheValid(cached, config)) {
return cached.data
}
const result = await this.deduplicate(config)
// 缓存结果
if (config.cache) {
this.cache.set(cacheKey, {
data: result,
timestamp: Date.now(),
ttl: config.cacheTTL || 300000
})
}
return result
}
// 预加载策略
preload(configs) {
configs.forEach(config => {
// 低优先级预加载
setTimeout(() => {
this.smartCache({
...config,
priority: 'low'
}).catch(error => {
console.warn('预加载失败:', error)
})
}, 100)
})
}
// 离线缓存
async offlineCache(config) {
const cacheKey = this.getCacheKey(config)
try {
const result = await this.smartCache(config)
// 存储到本地
try {
uni.setStorageSync(
`offline_${cacheKey}`,
{
data: result,
timestamp: Date.now()
}
)
} catch (storageError) {
console.warn('离线缓存存储失败:', storageError)
}
return result
} catch (error) {
// 网络失败时使用本地缓存
try {
const offline = uni.getStorageSync(`offline_${cacheKey}`)
if (offline && Date.now() - offline.timestamp < 86400000) { // 24小时
console.log('使用离线缓存数据')
return offline.data
}
} catch (storageError) {
console.warn('读取离线缓存失败:', storageError)
}
throw error
}
}
// 清理缓存
clearCache() {
this.cache.clear()
}
// 获取缓存状态
getCacheStats() {
return {
size: this.cache.size,
pendingRequests: this.pending.size
}
}
}
// 创建全局实例
const optimizer = new RequestOptimizer()
// 房源列表API优化
export const getHouseListOptimized = async (params) => {
return optimizer.smartCache({
url: '/api/house/list',
method: 'GET',
params,
cache: true,
cacheTTL: 300000 // 5分钟缓存
})
}
// 房源详情API优化
export const getHouseDetailOptimized = async (id) => {
return optimizer.offlineCache({
url: `/api/house/detail/${id}`,
method: 'GET',
cache: true,
cacheTTL: 600000 // 10分钟缓存
})
}
// 批量预加载
export const preloadHouseData = (houseIds) => {
const configs = houseIds.map(id => ({
url: `/api/house/detail/${id}`,
method: 'GET',
cache: true,
cacheTTL: 600000
}))
optimizer.preload(configs)
}
export default optimizer
图片优化的重要性
在房产应用中,图片是最重要的展示媒介之一。一个房源详情页面可能包含几十张高清图片,如何快速、流畅地展示这些图片,直接影响用户的浏览体验和购买决策。据统计,图片加载时间每增加1秒,用户流失率就会增加7%。
图片优化策略
关键技术点
{{placeholderText}}
重新加载
渲染性能直接影响用户的操作流畅度。房产应用通常需要展示大量的房源列表、图片网格、地图标点等内容,如果渲染性能不佳,会导致页面卡顿、滚动不流畅等问题,严重影响用户体验。
长列表性能挑战
房产应用的核心场景是房源列表展示,一个区域可能有成千上万套房源。如果使用传统的列表渲染方式,会面临以下问题:
虚拟滚动原理
虚拟滚动是解决长列表性能问题的最佳方案,其核心思想是:
实现关键点
加载中...
加载更多
懒加载的价值
图片懒加载是提升页面性能的重要手段,特别是对于房产应用这种图片密集的场景。通过懒加载,我们可以:
跨平台兼容挑战
uni-app需要适配多个平台,每个平台对懒加载的实现方式有所不同:
骨架屏的作用
骨架屏是提升用户体验的重要技术,它通过展示页面的基本结构来缓解用户等待的焦虑感:
// utils/lazy-load.js
class LazyLoad {
constructor(options = {}) {
this.options = {
threshold: 0.1,
rootMargin: '0px 0px 100px 0px',
...options
}
this.observer = null
this.targets = new WeakMap()
this.init()
}
init() {
// #ifdef H5
// 使用Intersection Observer API
if (typeof window !== 'undefined' && window.IntersectionObserver) {
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
this.options
)
} else {
// 降级到滚动监听
this.initScrollListener()
}
// #endif
// #ifndef H5
// 非H5平台使用uni-app的createIntersectionObserver
this.initUniObserver()
// #endif
}
// uni-app平台的观察者
initUniObserver() {
this.uniObserver = uni.createIntersectionObserver()
.relativeToViewport({ bottom: 100 })
.observe('.lazy-image', (res) => {
if (res.intersectionRatio > 0) {
this.loadUniImage(res.target)
}
})
}
observe(element, src) {
if (!element) return
this.targets.set(element, { src, loaded: false })
// #ifdef H5
if (this.observer) {
this.observer.observe(element)
}
// #endif
}
unobserve(element) {
if (!element) return
this.targets.delete(element)
// #ifdef H5
if (this.observer) {
this.observer.unobserve(element)
}
// #endif
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target)
}
})
}
loadImage(element) {
const target = this.targets.get(element)
if (!target || target.loaded) return
// #ifdef H5
const img = new Image()
img.onload = () => {
element.src = target.src
element.classList.add('loaded')
target.loaded = true
this.unobserve(element)
}
img.onerror = () => {
element.classList.add('error')
this.unobserve(element)
}
img.src = target.src
// #endif
// #ifndef H5
// 非H5平台直接设置src
element.src = target.src
target.loaded = true
// #endif
}
loadUniImage(element) {
const target = this.targets.get(element)
if (!target || target.loaded) return
element.src = target.src
target.loaded = true
}
// 降级方案
initScrollListener() {
this.checkVisible = this.debounce(() => {
this.targets.forEach((target, element) => {
if (!target.loaded && this.isElementVisible(element)) {
this.loadImage(element)
}
})
}, 100)
// #ifdef H5
window.addEventListener('scroll', this.checkVisible)
window.addEventListener('resize', this.checkVisible)
// #endif
}
isElementVisible(element) {
// #ifdef H5
const rect = element.getBoundingClientRect()
const windowHeight = window.innerHeight
return rect.top < windowHeight && rect.bottom > 0
// #endif
// #ifndef H5
// 非H5平台总是返回true,依赖uni-app的懒加载
return true
// #endif
}
debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
destroy() {
// #ifdef H5
if (this.observer) {
this.observer.disconnect()
}
if (this.checkVisible) {
window.removeEventListener('scroll', this.checkVisible)
window.removeEventListener('resize', this.checkVisible)
}
// #endif
// #ifndef H5
if (this.uniObserver) {
this.uniObserver.disconnect()
}
// #endif
}
}
// Vue插件
export default {
install(Vue) {
const lazyLoad = new LazyLoad()
Vue.directive('lazy', {
bind(el, binding) {
lazyLoad.observe(el, binding.value)
},
unbind(el) {
lazyLoad.unobserve(el)
}
})
Vue.prototype.$lazyLoad = lazyLoad
}
}
<template>
<view class="skeleton-container">
<view class="skeleton-item" v-for="n in count" :key="n">
<view class="skeleton-avatar">view>
<view class="skeleton-content">
<view class="skeleton-line long">view>
<view class="skeleton-line medium">view>
<view class="skeleton-line short">view>
view>
view>
view>
template>
<script>
export default {
name: 'HouseListSkeleton',
props: {
count: {
type: Number,
default: 5
}
}
}
script>
<style lang="scss" scoped>
.skeleton-container {
.skeleton-item {
display: flex;
padding: 30rpx;
border-bottom: 1px solid #f0f0f0;
}
.skeleton-avatar {
width: 200rpx;
height: 150rpx;
border-radius: 8rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-content {
flex: 1;
margin-left: 30rpx;
}
.skeleton-line {
height: 32rpx;
border-radius: 16rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
margin-bottom: 20rpx;
&.long { width: 100%; }
&.medium { width: 70%; }
&.short { width: 40%; }
}
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
style>
<template>
<view class="detail-skeleton">
<view class="skeleton-banner">view>
<view class="skeleton-info">
<view class="skeleton-line title">view>
<view class="skeleton-line subtitle">view>
<view class="skeleton-line price">view>
view>
<view class="skeleton-details">
<view class="skeleton-line" v-for="n in 6" :key="n">view>
view>
<view class="skeleton-images">
<view class="skeleton-image" v-for="n in 6" :key="n">view>
view>
view>
template>
<script>
export default {
name: 'HouseDetailSkeleton'
}
script>
<style lang="scss" scoped>
.detail-skeleton {
.skeleton-banner {
width: 100%;
height: 500rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}
.skeleton-info {
padding: 30rpx;
.skeleton-line {
height: 32rpx;
border-radius: 16rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
margin-bottom: 20rpx;
&.title { width: 80%; height: 40rpx; }
&.subtitle { width: 60%; }
&.price { width: 50%; height: 36rpx; }
}
}
.skeleton-details {
padding: 30rpx;
.skeleton-line {
height: 28rpx;
border-radius: 14rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
margin-bottom: 16rpx;
&:nth-child(odd) { width: 100%; }
&:nth-child(even) { width: 85%; }
}
}
.skeleton-images {
padding: 30rpx;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.skeleton-image {
width: 220rpx;
height: 160rpx;
border-radius: 8rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
margin-bottom: 20rpx;
}
}
}
@keyframes skeleton-loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
style>
内存管理是移动应用性能优化的重要组成部分。房产应用由于功能复杂、页面较多,容易出现内存泄漏问题。良好的内存管理不仅能防止应用崩溃,还能保证应用长时间运行的稳定性。
对于房产应用来说,内存管理尤为重要:
统一资源管理策略
为了有效防止内存泄漏,我们需要建立统一的资源管理机制:
关键实践原则
// utils/memory-manager.js
class MemoryManager {
constructor() {
this.timers = new Set()
this.observers = new Set()
this.eventListeners = new Map()
this.requestTasks = new Set()
this.subscriptions = new Set()
}
// 统一定时器管理
setTimeout(callback, delay) {
const id = setTimeout(() => {
callback()
this.timers.delete(id)
}, delay)
this.timers.add(id)
return id
}
setInterval(callback, delay) {
const id = setInterval(callback, delay)
this.timers.add(id)
return id
}
clearTimer(id) {
clearTimeout(id)
clearInterval(id)
this.timers.delete(id)
}
// 事件监听管理
addEventListener(element, event, handler) {
// #ifdef H5
element.addEventListener(event, handler)
// #endif
if (!this.eventListeners.has(element)) {
this.eventListeners.set(element, [])
}
this.eventListeners.get(element).push({
event,
handler
})
}
removeEventListener(element, event, handler) {
// #ifdef H5
element.removeEventListener(event, handler)
// #endif
const listeners = this.eventListeners.get(element)
if (listeners) {
const index = listeners.findIndex(
l => l.event === event && l.handler === handler
)
if (index > -1) {
listeners.splice(index, 1)
}
}
}
// 网络请求管理
addRequestTask(task) {
this.requestTasks.add(task)
}
removeRequestTask(task) {
this.requestTasks.delete(task)
}
// 观察者管理
addObserver(observer) {
this.observers.add(observer)
}
removeObserver(observer) {
this.observers.delete(observer)
}
// 订阅管理
addSubscription(subscription) {
this.subscriptions.add(subscription)
}
removeSubscription(subscription) {
this.subscriptions.delete(subscription)
}
// 清理所有资源
cleanup() {
// 清理定时器
this.timers.forEach(id => {
clearTimeout(id)
clearInterval(id)
})
this.timers.clear()
// 清理事件监听
this.eventListeners.forEach((listeners, element) => {
listeners.forEach(({ event, handler }) => {
// #ifdef H5
element.removeEventListener(event, handler)
// #endif
})
})
this.eventListeners.clear()
// 清理观察者
this.observers.forEach(observer => {
try {
observer.disconnect()
} catch (e) {
console.warn('Observer disconnect failed:', e)
}
})
this.observers.clear()
// 清理网络请求
this.requestTasks.forEach(task => {
try {
if (task.abort) {
task.abort()
}
} catch (e) {
console.warn('Request abort failed:', e)
}
})
this.requestTasks.clear()
// 清理订阅
this.subscriptions.forEach(subscription => {
try {
if (typeof subscription === 'function') {
subscription()
} else if (subscription.unsubscribe) {
subscription.unsubscribe()
}
} catch (e) {
console.warn('Subscription cleanup failed:', e)
}
})
this.subscriptions.clear()
}
// 获取内存使用情况
getMemoryStats() {
return {
timers: this.timers.size,
eventListeners: this.eventListeners.size,
observers: this.observers.size,
requestTasks: this.requestTasks.size,
subscriptions: this.subscriptions.size
}
}
}
// Vue混入
export const memoryMixin = {
beforeCreate() {
this.$memoryManager = new MemoryManager()
},
beforeDestroy() {
if (this.$memoryManager) {
this.$memoryManager.cleanup()
}
},
methods: {
$setTimeout(callback, delay) {
return this.$memoryManager.setTimeout(callback, delay)
},
$setInterval(callback, delay) {
return this.$memoryManager.setInterval(callback, delay)
},
$addEventListener(element, event, handler) {
this.$memoryManager.addEventListener(element, event, handler)
},
$addRequestTask(task) {
this.$memoryManager.addRequestTask(task)
},
$addObserver(observer) {
this.$memoryManager.addObserver(observer)
},
$addSubscription(subscription) {
this.$memoryManager.addSubscription(subscription)
}
}
}
// 页面级别的内存管理
export const pageMemoryMixin = {
onLoad() {
this.$memoryManager = new MemoryManager()
},
onUnload() {
if (this.$memoryManager) {
this.$memoryManager.cleanup()
}
},
methods: {
...memoryMixin.methods
}
}
export default MemoryManager
缓存是提升应用性能最有效的手段之一。对于房产应用来说,合理的缓存策略可以显著减少网络请求,提升数据加载速度,改善用户体验。同时,缓存还能在网络不稳定的情况下提供基本的离线功能。
价值体现
面临挑战
缓存层级设计
多级缓存系统通过建立不同层级的缓存,实现最优的性能和用户体验:
缓存策略原则
房产应用的缓存分类
// utils/cache-manager.js
class CacheManager {
constructor() {
this.memoryCache = new Map()
this.storageCache = this.loadStorageCache()
this.cleanupTimer = null
this.maxMemorySize = 100 // 最大内存缓存数量
this.maxStorageSize = 1024 * 1024 * 10 // 最大存储缓存大小 10MB
// 定期清理过期缓存
this.startCleanupTimer()
}
// 加载存储缓存
loadStorageCache() {
try {
const cache = uni.getStorageSync('app_cache')
return cache || {}
} catch (error) {
console.error('加载缓存失败:', error)
return {}
}
}
// 设置缓存
set(key, value, options = {}) {
const cacheItem = {
value,
timestamp: Date.now(),
ttl: options.ttl || 0,
persistent: options.persistent || false,
size: this.calculateSize(value)
}
// 内存缓存
this.setMemoryCache(key, cacheItem)
// 持久化缓存
if (cacheItem.persistent) {
this.setStorageCache(key, cacheItem)
}
}
// 设置内存缓存
setMemoryCache(key, item) {
// 检查内存缓存大小限制
if (this.memoryCache.size >= this.maxMemorySize) {
this.evictMemoryCache()
}
this.memoryCache.set(key, item)
}
// 设置存储缓存
setStorageCache(key, item) {
// 检查存储大小限制
if (this.getStorageSize() + item.size > this.maxStorageSize) {
this.evictStorageCache()
}
this.storageCache[key] = item
this.saveStorageCache()
}
// 计算数据大小
calculateSize(value) {
try {
return JSON.stringify(value).length * 2 // 粗略估算
} catch (error) {
return 0
}
}
// 获取存储缓存总大小
getStorageSize() {
return Object.values(this.storageCache).reduce((total, item) => {
return total + (item.size || 0)
}, 0)
}
// 内存缓存淘汰策略(LRU)
evictMemoryCache() {
const entries = Array.from(this.memoryCache.entries())
entries.sort((a, b) => a[1].timestamp - b[1].timestamp)
// 删除最老的缓存项
const [oldestKey] = entries[0]
this.memoryCache.delete(oldestKey)
}
// 存储缓存淘汰策略
evictStorageCache() {
const entries = Object.entries(this.storageCache)
entries.sort((a, b) => a[1].timestamp - b[1].timestamp)
// 删除最老的缓存项直到满足大小限制
while (this.getStorageSize() > this.maxStorageSize * 0.8 && entries.length > 0) {
const [oldestKey] = entries.shift()
delete this.storageCache[oldestKey]
}
}
// 获取缓存
get(key) {
// 先从内存缓存获取
let item = this.memoryCache.get(key)
// 再从存储缓存获取
if (!item) {
item = this.storageCache[key]
if (item) {
// 恢复到内存缓存
this.setMemoryCache(key, item)
}
}
if (!item) return null
// 检查过期
if (this.isExpired(item)) {
this.delete(key)
return null
}
// 更新访问时间
item.timestamp = Date.now()
return item.value
}
// 删除缓存
delete(key) {
this.memoryCache.delete(key)
delete this.storageCache[key]
this.saveStorageCache()
}
// 清空缓存
clear() {
this.memoryCache.clear()
this.storageCache = {}
this.saveStorageCache()
}
// 检查是否过期
isExpired(item) {
if (!item.ttl) return false
return Date.now() - item.timestamp > item.ttl
}
// 保存存储缓存
saveStorageCache() {
try {
uni.setStorageSync('app_cache', this.storageCache)
} catch (error) {
console.error('保存缓存失败:', error)
// 如果保存失败,可能是存储空间不足,执行清理
this.evictStorageCache()
try {
uni.setStorageSync('app_cache', this.storageCache)
} catch (retryError) {
console.error('重试保存缓存失败:', retryError)
}
}
}
// 清理过期缓存
cleanup() {
const now = Date.now()
// 清理内存缓存
for (const [key, item] of this.memoryCache.entries()) {
if (this.isExpired(item)) {
this.memoryCache.delete(key)
}
}
// 清理存储缓存
Object.keys(this.storageCache).forEach(key => {
const item = this.storageCache[key]
if (this.isExpired(item)) {
delete this.storageCache[key]
}
})
this.saveStorageCache()
}
// 启动清理定时器
startCleanupTimer() {
this.cleanupTimer = setInterval(() => {
this.cleanup()
}, 5 * 60 * 1000) // 5分钟清理一次
}
// 停止清理定时器
stopCleanupTimer() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer)
this.cleanupTimer = null
}
}
// 获取缓存统计
getStats() {
const memorySize = this.memoryCache.size
const storageSize = Object.keys(this.storageCache).length
const totalStorageSize = this.getStorageSize()
return {
memorySize,
storageSize,
totalSize: memorySize + storageSize,
storageBytes: totalStorageSize,
memoryUsage: (memorySize / this.maxMemorySize * 100).toFixed(2) + '%',
storageUsage: (totalStorageSize / this.maxStorageSize * 100).toFixed(2) + '%'
}
}
// 预热缓存
preload(data) {
Object.entries(data).forEach(([key, value]) => {
this.set(key, value, { ttl: 3600000 }) // 1小时缓存
})
}
// 销毁缓存管理器
destroy() {
this.stopCleanupTimer()
this.memoryCache.clear()
this.storageCache = {}
}
}
// 创建全局缓存管理器实例
const cacheManager = new CacheManager()
// 房产应用专用缓存方法
export const houseCache = {
// 房源列表缓存
setHouseList(params, data) {
const key = `house_list_${JSON.stringify(params)}`
cacheManager.set(key, data, { ttl: 300000, persistent: true }) // 5分钟缓存
},
getHouseList(params) {
const key = `house_list_${JSON.stringify(params)}`
return cacheManager.get(key)
},
// 房源详情缓存
setHouseDetail(id, data) {
const key = `house_detail_${id}`
cacheManager.set(key, data, { ttl: 600000, persistent: true }) // 10分钟缓存
},
getHouseDetail(id) {
const key = `house_detail_${id}`
return cacheManager.get(key)
},
// 用户信息缓存
setUserInfo(data) {
cacheManager.set('user_info', data, { ttl: 86400000, persistent: true }) // 24小时缓存
},
getUserInfo() {
return cacheManager.get('user_info')
},
// 城市数据缓存
setCityData(data) {
cacheManager.set('city_data', data, { ttl: 2592000000, persistent: true }) // 30天缓存
},
getCityData() {
return cacheManager.get('city_data')
}
}
export default cacheManager
性能监控是性能优化工作的重要环节,它帮助我们及时发现性能问题,量化优化效果,持续改进应用性能。对于房产应用这种业务复杂的应用,建立完善的性能监控体系尤为重要。
业务价值
技术价值
数据收集层
数据处理层
数据应用层
关键性能指标(KPI)
对于房产应用,我们需要重点关注以下性能指标:
用户体验指标
技术性能指标
业务性能指标
数据收集策略
// utils/performance-monitor.js
class PerformanceMonitor {
constructor() {
this.metrics = {
pageLoadTime: 0,
apiResponseTime: new Map(),
renderTime: 0,
memoryUsage: 0,
errorCount: 0,
crashCount: 0,
userActions: []
}
this.reportQueue = []
this.isReporting = false
this.reportInterval = null
this.init()
}
init() {
this.startPageLoadMonitor()
this.startMemoryMonitor()
this.startErrorMonitor()
this.startReportTimer()
}
// 页面加载监控
startPageLoadMonitor() {
const startTime = Date.now()
// 监听应用生命周期
uni.$on('onShow', () => {
this.metrics.pageLoadTime = Date.now() - startTime
})
// 监听页面性能
// #ifdef H5
if (performance.timing) {
const timing = performance.timing
this.metrics.pageLoadTime = timing.loadEventEnd - timing.navigationStart
}
// #endif
}
// API响应时间监控
monitorAPI(url, startTime, success = true) {
const endTime = Date.now()
const responseTime = endTime - startTime
if (!this.metrics.apiResponseTime.has(url)) {
this.metrics.apiResponseTime.set(url, [])
}
this.metrics.apiResponseTime.get(url).push({
time: responseTime,
success,
timestamp: endTime
})
// 只保留最近100条记录
const records = this.metrics.apiResponseTime.get(url)
if (records.length > 100) {
records.splice(0, records.length - 100)
}
// 记录慢请求
if (responseTime > 3000) {
this.reportError({
type: 'slow_api',
url,
responseTime,
timestamp: endTime
})
}
}
// 内存使用监控
startMemoryMonitor() {
setInterval(() => {
// #ifdef APP-PLUS
if (plus.os.name === 'Android') {
plus.runtime.getProperty(plus.runtime.appid, (info) => {
this.metrics.memoryUsage = info.memory
// 内存使用过高警告
if (info.memory > 200 * 1024 * 1024) { // 200MB
this.reportError({
type: 'high_memory',
memoryUsage: info.memory,
timestamp: Date.now()
})
}
})
}
// #endif
// #ifdef H5
if (performance.memory) {
this.metrics.memoryUsage = performance.memory.usedJSHeapSize
// 内存使用过高警告
if (performance.memory.usedJSHeapSize > 50 * 1024 * 1024) { // 50MB
this.reportError({
type: 'high_memory',
memoryUsage: performance.memory.usedJSHeapSize,
timestamp: Date.now()
})
}
}
// #endif
}, 30000) // 30秒检查一次
}
// 错误监控
startErrorMonitor() {
// 全局错误捕获
uni.onError((error) => {
this.metrics.errorCount++
this.reportError({
type: 'runtime',
message: error.message,
stack: error.stack,
timestamp: Date.now()
})
})
// #ifdef H5
// 未处理的Promise拒绝
window.addEventListener('unhandledrejection', (event) => {
this.metrics.errorCount++
this.reportError({
type: 'promise',
message: event.reason,
timestamp: Date.now()
})
})
// JS错误监控
window.addEventListener('error', (event) => {
this.metrics.errorCount++
this.reportError({
type: 'js_error',
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
timestamp: Date.now()
})
})
// #endif
}
// 渲染性能监控
measureRender(name, fn) {
const startTime = Date.now()
return Promise.resolve(fn()).then(result => {
const endTime = Date.now()
const renderTime = endTime - startTime
console.log(`渲染时间 ${name}: ${renderTime.toFixed(2)}ms`)
if (renderTime > 16.67) { // 超过一帧时间
console.warn(`渲染性能警告 ${name}: ${renderTime.toFixed(2)}ms`)
this.reportError({
type: 'slow_render',
name,
renderTime,
timestamp: endTime
})
}
return result
})
}
// 用户行为监控
trackUserAction(action, params = {}) {
this.metrics.userActions.push({
action,
params,
timestamp: Date.now()
})
// 只保留最近50条记录
if (this.metrics.userActions.length > 50) {
this.metrics.userActions.splice(0, this.metrics.userActions.length - 50)
}
}
// 崩溃监控
trackCrash(error) {
this.metrics.crashCount++
this.reportError({
type: 'crash',
message: error.message,
stack: error.stack,
userActions: this.metrics.userActions.slice(-10), // 最近10个用户行为
timestamp: Date.now()
})
}
// 上报错误
reportError(error) {
this.reportQueue.push({
...error,
deviceInfo: this.getDeviceInfo(),
appInfo: this.getAppInfo()
})
// 立即上报严重错误
if (error.type === 'crash' || error.type === 'js_error') {
this.flushReports()
}
}
// 获取设备信息
getDeviceInfo() {
const systemInfo = uni.getSystemInfoSync()
return {
platform: systemInfo.platform,
model: systemInfo.model,
version: systemInfo.version,
brand: systemInfo.brand,
screenWidth: systemInfo.screenWidth,
screenHeight: systemInfo.screenHeight,
pixelRatio: systemInfo.pixelRatio,
language: systemInfo.language,
system: systemInfo.system
}
}
// 获取应用信息
getAppInfo() {
// #ifdef APP-PLUS
return {
appId: plus.runtime.appid,
version: plus.runtime.version,
versionCode: plus.runtime.versionCode
}
// #endif
// #ifdef H5
return {
userAgent: navigator.userAgent,
url: location.href
}
// #endif
// #ifdef MP
return {
appId: uni.getAccountInfoSync().miniProgram.appId,
version: uni.getAccountInfoSync().miniProgram.version,
envVersion: uni.getAccountInfoSync().miniProgram.envVersion
}
// #endif
return {}
}
// 启动定时上报
startReportTimer() {
this.reportInterval = setInterval(() => {
this.flushReports()
}, 60000) // 每分钟上报一次
}
// 批量上报
flushReports() {
if (this.reportQueue.length === 0 || this.isReporting) {
return
}
this.isReporting = true
const reports = this.reportQueue.splice(0, 10) // 每次最多上报10条
uni.request({
url: '/api/monitor/errors',
method: 'POST',
data: reports,
success: () => {
console.log('错误上报成功')
},
fail: (error) => {
console.error('错误上报失败:', error)
// 失败的报告重新加入队列
this.reportQueue.unshift(...reports)
},
complete: () => {
this.isReporting = false
// 如果还有待上报的数据,继续上报
if (this.reportQueue.length > 0) {
setTimeout(() => {
this.flushReports()
}, 5000)
}
}
})
}
// 上报性能数据
reportMetrics() {
const metrics = this.getMetrics()
uni.request({
url: '/api/monitor/performance',
method: 'POST',
data: metrics,
success: () => {
console.log('性能数据上报成功')
},
fail: (error) => {
console.error('性能数据上报失败:', error)
}
})
}
// 获取性能指标
getMetrics() {
const apiMetrics = {}
this.metrics.apiResponseTime.forEach((records, url) => {
const successRecords = records.filter(r => r.success)
const failRecords = records.filter(r => !r.success)
if (successRecords.length > 0) {
const times = successRecords.map(r => r.time)
apiMetrics[url] = {
count: successRecords.length,
avg: times.reduce((a, b) => a + b, 0) / times.length,
max: Math.max(...times),
min: Math.min(...times),
successRate: (successRecords.length / records.length * 100).toFixed(2) + '%'
}
}
})
return {
...this.metrics,
apiResponseTime: apiMetrics,
deviceInfo: this.getDeviceInfo(),
appInfo: this.getAppInfo(),
timestamp: Date.now()
}
}
// 销毁监控器
destroy() {
if (this.reportInterval) {
clearInterval(this.reportInterval)
}
// 上报剩余数据
this.flushReports()
}
}
// 创建全局性能监控实例
const performanceMonitor = new PerformanceMonitor()
// 页面性能监控混入
export const performanceMixin = {
onLoad() {
this.pageStartTime = Date.now()
performanceMonitor.trackUserAction('page_enter', {
route: this.$route?.path || this.$options.name
})
},
onShow() {
if (this.pageStartTime) {
const loadTime = Date.now() - this.pageStartTime
performanceMonitor.trackUserAction('page_show', {
loadTime,
route: this.$route?.path || this.$options.name
})
}
},
onHide() {
performanceMonitor.trackUserAction('page_hide', {
route: this.$route?.path || this.$options.name
})
},
onUnload() {
performanceMonitor.trackUserAction('page_exit', {
route: this.$route?.path || this.$options.name
})
}
}
export default performanceMonitor
综合应用场景
以下是一个房源列表页面的完整实现示例,它综合运用了本文介绍的各种性能优化技术。这个示例展示了如何在实际项目中将性能优化理论转化为可执行的代码。
优化技术集成
该示例集成了以下优化技术:
实现特点
<template>
<view class="house-list">
<view class="search-header">
<input
v-model="searchKeyword"
placeholder="请输入楼盘名称"
@confirm="handleSearch"
/>
view>
<house-list-skeleton v-if="loading && list.length === 0" :count="5" />
<infinite-list
v-else
:items="list"
:has-more="hasMore"
:load-more="loadMore"
@scroll="handleScroll"
>
<template #default="{ item }">
<house-item
:house="item"
@click="goToDetail(item.id)"
@favorite="handleFavorite(item)"
/>
template>
infinite-list>
<empty-state v-if="!loading && list.length === 0" />
view>
template>
<script>
import { performanceMixin, pageMemoryMixin } from '@/utils/mixins'
import { getHouseListOptimized, preloadHouseData } from '@/api/house'
import { houseCache } from '@/utils/cache-manager'
import performanceMonitor from '@/utils/performance-monitor'
export default {
name: 'HouseList',
mixins: [performanceMixin, pageMemoryMixin],
data() {
return {
list: [],
loading: false,
hasMore: true,
currentPage: 1,
searchKeyword: '',
searchParams: {}
}
},
onLoad(options) {
this.initPage(options)
},
onShow() {
// 检查是否需要刷新数据
this.checkDataFreshness()
},
onPullDownRefresh() {
this.refreshData()
},
methods: {
async initPage(options) {
try {
// 解析搜索参数
this.searchParams = this.parseSearchParams(options)
// 尝试从缓存加载数据
const cachedData = houseCache.getHouseList(this.searchParams)
if (cachedData) {
this.list = cachedData.list
this.hasMore = cachedData.hasMore
}
// 加载最新数据
await this.loadData()
// 预加载相关数据
this.preloadRelatedData()
} catch (error) {
performanceMonitor.trackCrash(error)
this.showError('页面加载失败')
}
},
async loadData(refresh = false) {
if (this.loading) return
this.loading = true
const startTime = Date.now()
try {
const params = {
...this.searchParams,
page: refresh ? 1 : this.currentPage,
pageSize: 20
}
const result = await getHouseListOptimized(params)
// 监控API响应时间
performanceMonitor.monitorAPI('/api/house/list', startTime, true)
if (refresh) {
this.list = result.list
this.currentPage = 2
} else {
this.list.push(...result.list)
this.currentPage++
}
this.hasMore = result.hasMore
// 缓存数据
houseCache.setHouseList(params, {
list: this.list,
hasMore: this.hasMore
})
// 预加载房源详情
if (result.list.length > 0) {
const houseIds = result.list.slice(0, 5).map(house => house.id)
preloadHouseData(houseIds)
}
} catch (error) {
performanceMonitor.monitorAPI('/api/house/list', startTime, false)
this.showError('加载失败,请重试')
} finally {
this.loading = false
if (refresh) {
uni.stopPullDownRefresh()
}
}
},
async loadMore() {
if (!this.hasMore || this.loading) return
await this.loadData()
},
async refreshData() {
await this.loadData(true)
},
handleSearch() {
performanceMonitor.trackUserAction('search', {
keyword: this.searchKeyword
})
this.searchParams.keyword = this.searchKeyword
this.currentPage = 1
this.refreshData()
},
goToDetail(houseId) {
performanceMonitor.trackUserAction('view_detail', {
houseId
})
uni.navigateTo({
url: `/house-package/detail/index?id=${houseId}`
})
},
handleFavorite(house) {
performanceMonitor.trackUserAction('favorite', {
houseId: house.id,
action: house.favorited ? 'remove' : 'add'
})
// 收藏逻辑
this.toggleFavorite(house)
},
handleScroll(e) {
// 记录用户滚动行为
performanceMonitor.trackUserAction('scroll', {
scrollTop: e.detail.scrollTop
})
},
checkDataFreshness() {
// 检查数据是否需要更新
const lastUpdate = uni.getStorageSync('house_list_last_update')
const now = Date.now()
if (!lastUpdate || now - lastUpdate > 300000) { // 5分钟
this.refreshData()
uni.setStorageSync('house_list_last_update', now)
}
},
preloadRelatedData() {
// 预加载相关数据
this.$setTimeout(() => {
// 预加载地区数据
this.$api.region.getList().catch(() => {})
// 预加载筛选条件
this.$api.filter.getOptions().catch(() => {})
}, 1000)
},
parseSearchParams(options) {
// 解析URL参数
return {
cityId: options.cityId || '',
priceRange: options.priceRange || '',
houseType: options.houseType || ''
}
},
toggleFavorite(house) {
// 实现收藏/取消收藏逻辑
house.favorited = !house.favorited
},
showError(message) {
uni.showToast({
title: message,
icon: 'none',
duration: 2000
})
}
}
}
script>
<style lang="scss" scoped>
.house-list {
height: 100vh;
display: flex;
flex-direction: column;
.search-header {
padding: 20rpx;
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
}
}
style>
经过系统的性能优化实践,我们总结出一套完整的最佳实践指南。这些实践经验来自于真实的房产应用开发项目,具有很强的实用性和可操作性。
性能优化不是一个孤立的技术问题,而是需要从产品设计、技术架构、开发实现、运营监控等多个环节进行系统性考虑。只有建立完整的性能管理体系,才能持续保证应用的高性能表现。
检查清单的价值
这个检查清单是我们在多个房产项目中总结出来的经验,它可以帮助开发团队:
使用建议
启动性能
网络性能
渲染性能
内存管理
缓存策略
性能监控
指标设定的重要性
明确的性能指标是性能优化工作的北极星,它帮助我们:
指标体系设计原则
用户体验指标
技术指标
持续优化的必要性
性能优化不是一次性的工作,而是一个持续的过程。随着业务的发展、用户需求的变化、技术的演进,我们需要不断地评估和改进应用的性能表现。
优化工作的长期性
房产应用的性能优化具有以下特点:
建立长效机制
定期性能评估
版本迭代优化
团队协作
工具和自动化
性能优化实践的收获
经过系统的性能优化实践,我们不仅提升了应用的技术性能,更重要的是建立了完整的性能管理体系。这个体系包括技术手段、流程规范、监控机制和团队协作等多个方面。
实际效果验证
在实际项目中应用这套优化方案后,我们取得了显著的效果:
优化工作的核心要素
通过以上完整的性能优化方案,uni-app房产应用可以在各个平台上提供流畅、稳定的用户体验。关键在于:
这套优化方案不仅适用于房产行业应用,也可以作为其他uni-app项目的性能优化参考。随着技术的发展和业务需求的变化,需要持续迭代和完善优化策略,确保应用始终保持最佳性能状态。
未来发展方向
随着技术的不断演进,性能优化也将面临新的挑战和机遇:
持续学习与实践
性能优化是一个需要持续学习和实践的领域。我们鼓励开发者:
希望这篇文章能为大家的uni-app性能优化工作提供有价值的参考,让我们一起为用户创造更优秀的移动应用体验。