uniapp基础(四)性能优化
目录
1.UniApp 优化页面加载速度
减少资源体积
分包加载
预加载
优化渲染性能
组件按需引入
优化网络请求
缓存
懒加载与虚拟列表
骨架屏
防抖/节流
2.如何减少小程序包体积
代码优化与分包加载
图片与资源压缩
组件按需引入
代码复用与公共库优化
构建配置优化
3.优化数据请求的策略
缓存策略
请求合并
懒加载与分页
防抖与节流
预加载与预取
离线优先策略
错误重试机制
4.如何避免页面渲染卡顿
优化数据加载方式
v-if/v-show
使用虚拟列表优化长列表
避免频繁setData操作
图片资源优化
合理使用CSS动画
组件级别优化
预加载关键资源
使用Web Worker处理密集计算
5.UniApp 中的图片懒加载
使用 image 组件的 lazy-load 属性
自定义懒加载逻辑(适用于多端)
使用第三方插件
注意事项
6.如何优化内存使用
减少全局变量和大型对象
合理使用v-if和v-for
及时销毁定时器和监听器
图片资源优化
使用虚拟列表处理长列表
避免频繁setData
内存泄漏检测工具
组件按需加载
7.web Workers
支持情况分析
实现建议
替代方案
8.代码分割
使用动态导入语法
配置Webpack的splitChunks
按需加载第三方库
分包加载
注意事项
9.优化动画性能的技巧
减少重绘和回流
使用requestAnimationFrame
降低动画复杂度
避免频繁的JS操作DOM
优化SVG和Canvas动画
10.监控小程序性能
使用微信开发者工具的性能面板
接入微信官方性能监控API
埋点关键性能指标
服务器端日志分析
典型性能优化示例
首屏加载优化
渲染性能优化
内存管理
1.UniApp 优化页面加载速度
减少资源体积
压缩图片和静态资源,使用工具如 TinyPNG 或 WebP 格式减小图片体积。对于图标,优先使用字体图标(如 iconfont)替代图片。
<template><view><!-- 使用 WebP 格式图片 --><image src="/static/example.webp"></image><!-- 使用字体图标 --><text class="iconfont icon-home"></text></view>
</template>
分包加载
将非首屏必需的页面或组件拆分为独立分包,减少主包体积。在 pages.json
中配置分包规则:
{"subPackages": [{"root": "subpackage","pages": [{"path": "detail", "style": {"navigationBarTitleText": "详情页"}}]}]
}
预加载
在 pages.json
中配置 preloadRule
预加载关键页面,提前加载后续可能访问的页面资源。
"preloadRule": {"pages/index/index": {"network": "all","packages": ["subpackage"]}
}
在页面 onLoad
阶段预加载后续可能用到的资源。通过 uni.preloadPage
预加载目标页面:
onLoad() {uni.preloadPage({url: '/pages/subpage/detail'});
}
在 onLoad
生命周期提前请求页面核心数据,避免渲染阻塞:
export default {onLoad() {this.fetchCoreData()},methods: {async fetchCoreData() {const res = await uni.request({ url: '/api/core-data' })this.data = res.data}}
}
优化渲染性能
复杂页面采用虚拟列表技术,通过 uni-list
组件只渲染可视区域内的条目。长列表避免使用 v-for
直接渲染全部数据,改用分页加载或滚动加载。
组件按需引入
避免全局注册未使用的组件,在页面级按需引入:
import customComponent from '@/components/custom-component.vue'export default {components: { customComponent }
}
非首屏必需的组件采用异步加载,通过 () => import()
动态导入。对于复杂组件,使用 uni.createComponent
延迟初始化。
优化网络请求
合并接口请求,使用缓存策略。通过 uni.setStorageSync
缓存已获取数据:
// 缓存示例
getData() {const cache = uni.getStorageSync('dataCache');if(cache) return Promise.resolve(cache);return api.fetch().then(res => {uni.setStorageSync('dataCache', res);return res;});
}
缓存
对稳定接口数据使用 uni.setStorage
缓存,减少重复请求:
async getData() {const cacheKey = 'cached_data'let data = uni.getStorageSync(cacheKey)if (!data) {data = await uni.request({ url: '/api/data' })uni.setStorageSync(cacheKey, data)}return data
}
懒加载与虚拟列表
非立即需要的组件使用异步引用。修改组件引入方式为动态加载:
components: {'lazy-component': () => import('@/components/lazy-component.vue')
}
长列表采用分页加载或虚拟滚动。使用 uni.$on
监听滚动事件分批请求数据:
// 滚动加载示例
onReachBottom() {if(this.loading) return;this.page++;this.fetchData();
}
长列表使用 uv-list
或 mescroll
等组件实现懒加载,避免一次性渲染大量节点。
<template><uv-list:data="listData"@loadmore="loadMore"></uv-list>
</template><script>
export default {data() {return {listData: []}},methods: {loadMore() {// 分批加载数据this.listData = [...this.listData, ...newData]}}
}
</script>
骨架屏
使用骨架屏占位提升用户体验,在 v-if/v-else
切换真实内容:
<template><skeleton v-if="loading"></skeleton><div v-else>实际内容</div>
</template>
防抖/节流
避免频繁触发页面重绘。对于大数据量计算使用防抖或节流:
// 函数节流示例
const throttle = (fn, delay) => {let last = 0;return function() {const now = Date.now();if(now - last > delay) {fn.apply(this, arguments);last = now;}}
}
2.如何减少小程序包体积
代码优化与分包加载
通过删除无用代码、注释、日志和未使用的图片资源,可以有效减少包体积。例如,使用工具如webpack-bundle-analyzer
分析依赖关系,移除冗余库。对于大型项目,采用分包加载策略,将非核心功能拆分为子包,按需加载。
// 分包配置示例(app.json)
{"subPackages": [{"root": "packageA","pages": ["pages/cat", "pages/dog"]}]
}
图片与资源压缩
将图片转换为WebP格式,压缩率比PNG/JPG高30%-70%。使用工具如TinyPNG
或imagemin
自动化压缩。静态资源优先使用网络CDN链接,避免打包进本地。
// 使用CDN图片示例(WXML)
<image src="https://example.com/image.webp" mode="aspectFit" />
组件按需引入
避免全量引入第三方组件库,仅导入需要的组件。以Vant Weapp为例:
// 错误示例:全量引入
import Vant from 'vant-weapp';// 正确示例:按需引入
import Button from 'vant-weapp/lib/button';
代码复用与公共库优化
提取公共代码到utils
目录,减少重复模块。对高频使用的工具函数,考虑使用更轻量的替代库,如用dayjs
替换moment.js
。
// 公共函数示例(utils/format.js)
export function formatDate(date) {return new Date(date).toLocaleString();
}
构建配置优化
在project.config.json
中开启代码压缩和Tree Shaking:
{"setting": {"minifyWXML": true,"minifyWXSS": true,"minifyJS": true,"uglifyFileName": true}
}
3.优化数据请求的策略
缓存策略
利用浏览器缓存或服务端缓存存储重复请求的数据,减少冗余请求
// 前端 localStorage 缓存示例
async function fetchWithCache(url) {const cachedData = localStorage.getItem(url);if (cachedData) return JSON.parse(cachedData);const response = await fetch(url);const data = await response.json();localStorage.setItem(url, JSON.stringify(data));return data;
}
请求合并
将多个小请求合并为一个批量请求,减少网络开销。适用于高频但低数据量的场景。
// 批量请求示例(假设后端支持 /batch 接口)
async function batchRequests(endpoints) {const response = await fetch('/batch', {method: 'POST',body: JSON.stringify({ endpoints })});return response.json();
}
懒加载与分页
按需加载数据,初始只请求必要部分,后续通过分页或滚动加载更多。
// 分页加载示例
let currentPage = 1;
async function loadNextPage() {const data = await fetch(`/api/items?page=${currentPage}`);currentPage++;return data;
}
防抖与节流
控制高频请求的频率,避免短时间内重复触发。
// 防抖示例(延迟请求直到停止操作)
function debounceFetch(query, delay) {let timer;return function() {clearTimeout(timer);timer = setTimeout(() => fetchData(query), delay);};
}
预加载与预取
通过 <link rel="preload">
或预测用户行为提前加载资源。
<!-- 预加载关键 API -->
<link rel="preload" href="/api/critical-data" as="fetch">
离线优先策略
使用 Service Worker 实现离线缓存,优先返回本地数据。
// Service Worker 缓存示例
self.addEventListener('fetch', (event) => {event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
});
错误重试机制
对失败请求实现指数退避重试,提升弱网可靠性。
async function fetchWithRetry(url, retries = 3) {try {const response = await fetch(url);if (!response.ok) throw new Error();return response;} catch (err) {if (retries <= 0) throw err;await new Promise(res => setTimeout(res, 1000 * (4 - retries)));return fetchWithRetry(url, retries - 1);}
}
4.如何避免页面渲染卡顿
优化数据加载方式
避免在页面onLoad
或onShow
中同步加载大量数据,采用分页或懒加载。以下是一个分页加载示例:
// script部分
export default {data() {return {list: [],page: 1,loading: false}},onLoad() {this.loadData()},onReachBottom() {if (!this.loading) {this.page++this.loadData()}},methods: {async loadData() {this.loading = trueconst res = await uni.request({url: 'api/list',data: { page: this.page }})this.list = [...this.list, ...res.data]this.loading = false}}
}
v-if/v-show
使用v-if
替代v-show
控制非必要元素的显示,避免渲染隐藏节点:
<template><view><view v-if="showComplexComponent"></view><button @click="toggle">切换显示</button></view>
</template><script>
export default {data() {return {showComplexComponent: false}},methods: {toggle() {this.showComplexComponent = !this.showComplexComponent}}
}
</script>
使用虚拟列表优化长列表
对于超长列表推荐使用uni-list
组件或实现虚拟滚动:
<template><view><uni-list :data="bigList" :height="800" :item-size="80"><template v-slot="{ item }"><view class="list-item">{{ item.name }}</view></template></uni-list></view>
</template>
避免频繁setData操作
合并数据更新,减少渲染次数:
// 不推荐写法
this.a = 1
this.b = 2
this.c = 3// 推荐写法
this.$set(this, {a: 1,b: 2,c: 3
})
图片资源优化
使用合适的图片尺寸和格式,添加懒加载:
<image lazy-load :src="imgUrl" mode="aspectFill"class="optimized-image"
></image>
合理使用CSS动画
避免使用高耗能的CSS属性,优先使用transform和opacity:
/* 推荐 */
.animate {transition: transform 0.3s ease;
}
.animate:hover {transform: scale(1.05);
}/* 不推荐 */
.expensive-animate {transition: all 0.3s ease;
}
.expensive-animate:hover {width: 110%;height: 110%;
}
组件级别优化
对于复杂组件使用v-once
或Object.freeze
:
<template><view><static-component v-once></static-component><dynamic-component :data="frozenData"></dynamic-component></view>
</template><script>
export default {data() {return {frozenData: Object.freeze(largeStaticData)}}
}
</script>
预加载关键资源
在应用启动时预加载必要资源:
// 在App.vue的onLaunch中
uni.preloadPage({ url: '/pages/important/page' })
uni.downloadFile({url: 'critical-asset.jpg',success: () => console.log('预加载完成')
})
使用Web Worker处理密集计算
将CPU密集型任务移出主线程:
// worker.js
self.onmessage = function(e) {const result = heavyComputation(e.data)self.postMessage(result)
}// 页面中
const worker = new Worker('worker.js')
worker.postMessage(data)
worker.onmessage = function(e) {this.result = e.data
}
5.UniApp 中的图片懒加载
使用 image
组件的 lazy-load
属性
在 UniApp 中,可以通过设置 image
组件的 lazy-load
属性为 true
来实现图片懒加载。这种方式仅在小程序平台(如微信小程序)中有效。
<image src="your-image-url" lazy-load="true"mode="aspectFill"
></image>
自定义懒加载逻辑(适用于多端)
如果需要跨平台支持(如 H5 和 App),可以通过监听滚动事件或使用 Intersection Observer API 实现自定义懒加载逻辑。
<template><view><image v-for="(item, index) in list" :key="index" :src="item.isVisible ? item.url : placeholder"@load="handleImageLoad"></image></view>
</template><script>
export default {data() {return {list: [{ url: 'image1.jpg', isVisible: false },{ url: 'image2.jpg', isVisible: false }],placeholder: 'placeholder.png'}},mounted() {this.initLazyLoad();window.addEventListener('scroll', this.handleScroll);},methods: {initLazyLoad() {// 使用 Intersection Observer 或自定义滚动逻辑this.checkVisibility();},checkVisibility() {const windowHeight = window.innerHeight;this.list.forEach((item, index) => {const query = uni.createSelectorQuery().in(this);query.select(`image:nth-child(${index + 1})`).boundingClientRect(data => {if (data && data.top < windowHeight) {this.list[index].isVisible = true;}}).exec();});},handleScroll() {this.checkVisibility();}}
}
</script>
使用第三方插件
UniApp 支持使用第三方插件如 vue-lazyload
来实现更复杂的懒加载功能。
安装插件:
npm install vue-lazyload
配置插件:
// main.js
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload, {preLoad: 1.3,error: 'error.png',loading: 'loading.gif',attempt: 1
});
使用插件:
<template><img v-lazy="imageUrl">
</template>
注意事项
- 小程序平台的
lazy-load
属性仅对image
组件有效,且需要在小程序配置中开启相关支持。 - 自定义懒加载逻辑需要考虑性能优化,避免频繁触发滚动事件。
- 第三方插件可能在某些平台上有兼容性问题,需测试验证。
6.如何优化内存使用
减少全局变量和大型对象
避免在全局作用域中定义大型对象或数组,改用局部变量或模块化封装。全局变量会持续占用内存,直到应用关闭。例如:
// 不推荐
const largeData = [...]; // 全局变量
export default {data() {return { list: largeData }}
}// 推荐
export default {data() {return { list: null }},created() {this.fetchData(); // 按需加载},methods: {fetchData() {const localData = [...]; // 局部变量this.list = localData;}}
}
合理使用v-if
和v-for
避免同时使用v-if
和v-for
,优先通过计算属性过滤数据。频繁的DOM操作会导致内存波动。
<!-- 不推荐 -->
<view v-for="item in list" v-if="item.active"></view><!-- 推荐 -->
<view v-for="item in filteredList"></view><script>
computed: {filteredList() {return this.list.filter(item => item.active);}
}
</script>
及时销毁定时器和监听器
在组件生命周期结束时手动清除定时器和全局事件监听,防止内存泄漏。
export default {data() {return {timer: null}},mounted() {this.timer = setInterval(() => {}, 1000);uni.onWindowResize(() => {});},beforeDestroy() {clearInterval(this.timer);uni.offWindowResize(() => {});}
}
图片资源优化
使用合适的图片格式(如WebP),对大图进行懒加载或分片加载。通过CSS雪碧图减少HTTP请求。
<!-- 懒加载示例 -->
<image lazy-load :src="imageUrl" mode="aspectFit"></image><!-- 使用雪碧图 -->
<view class="sprite-icon"></view>
<style>
.sprite-icon {background: url('/static/sprite.png') no-repeat;background-position: 0 -100px;
}
</style>
使用虚拟列表处理长列表
对于超长列表数据,采用<scroll-view>
配合动态渲染,仅显示可视区域内的元素。
<scroll-view scroll-y style="height: 500px;"><view v-for="(item, index) in visibleData" :key="index">{{ item.content }}</view>
</scroll-view><script>
export default {data() {return {fullList: [...], // 完整数据startIndex: 0,pageSize: 20}},computed: {visibleData() {return this.fullList.slice(this.startIndex, this.startIndex + this.pageSize);}},methods: {handleScroll(e) {// 根据滚动位置动态更新startIndex}}
}
</script>
避免频繁setData
在小程序中,合并多次数据更新,减少通信开销。Vue版本需注意响应式数据的批量更新。
// 不推荐
this.list.push(newItem);
this.page++;
this.loading = false;// 推荐
this.$set(this, 'list', [...this.list, newItem]);
this.$nextTick(() => {this.page++;this.loading = false;
});
内存泄漏检测工具
通过Chrome DevTools的Memory面板进行堆快照分析,查找分离的DOM节点和未释放的闭包。在HBuilderX中启用调试模式查看内存占用曲线。
组件按需加载
使用分包加载和异步组件,延迟非必要组件的初始化。
// 异步组件示例
const lazyComponent = () => import('@/components/lazy-component.vue');// pages.json分包配置
{"subPackages": [{"root": "subpackage","pages": [...]}]
}
7.web Workers
Web Workers 是浏览器提供的多线程技术,允许在后台运行脚本而不阻塞主线程。UniApp 作为一个跨平台框架,对 Web Workers 的支持取决于运行环境。
支持情况分析
H5 平台
- 完全支持标准的 Web Workers API,可以直接使用。
- 创建方式与普通 Web 项目一致:
const worker = new Worker('worker.js');
小程序平台
- 微信小程序使用
Worker
接口,与标准 Web Workers 类似但存在差异。 - 需要在小程序项目的配置文件中声明:
"workers": "workers"
- 创建方式:
const worker = wx.createWorker('workers/worker.js');
App 平台
- 部分支持,具体实现依赖原生渲染引擎。
- 可能存在性能限制或兼容性问题。
实现建议
通用方案
- 使用条件编译区分平台:
// #ifdef H5 const worker = new Worker('worker.js'); // #endif// #ifdef MP-WEIXIN const worker = wx.createWorker('workers/worker.js'); // #endif
注意事项
- 不同平台的 Worker 文件路径处理方式不同。
- 线程间通信的数据需要可序列化。
- 复杂计算任务建议优先考虑云函数方案。
替代方案
对于需要更广泛兼容性的场景,可以考虑:
- 使用
setTimeout
或requestIdleCallback
分片任务 - 将计算密集型任务移至服务端
- 使用 WebAssembly 处理性能关键代码
8.代码分割
在UniApp中实现代码分割,主要通过Webpack的配置和动态导入(Dynamic Import)来完成。以下是一些具体的方法:
使用动态导入语法
在页面或组件中,使用import()
动态导入模块,Webpack会自动将其打包为单独的chunk
const module = () => import('@/components/MyComponent.vue');
配置Webpack的splitChunks
在UniApp项目的vue.config.js
中,可以通过configureWebpack
选项配置Webpack的splitChunks
。
module.exports = {configureWebpack: {optimization: {splitChunks: {chunks: 'all',minSize: 20000,maxSize: 0,minChunks: 1,maxAsyncRequests: 30,maxInitialRequests: 30,automaticNameDelimiter: '~',cacheGroups: {vendors: {test: /[\\/]node_modules[\\/]/,priority: -10},default: {minChunks: 2,priority: -20,reuseExistingChunk: true}}}}}
};
按需加载第三方库
对于较大的第三方库,可以使用动态导入按需加载
const loadLibrary = () => import('lodash');
分包加载
UniApp支持分包加载,可以在pages.json
中配置分包:
{"subPackages": [{"root": "subpackage","pages": [{"path": "page1","style": {}}]}]
}
注意事项
- 动态导入的模块会生成单独的chunk文件,需确保服务器正确配置以支持按需加载。
- 分包加载适用于较大的功能模块,可以显著提升首页加载速度。
- 在H5环境中,Webpack的代码分割效果更明显;在小程序环境中,分包加载是更常用的优化手段。
9.优化动画性能的技巧
减少重绘和回流
使用CSS的transform
和opacity
属性进行动画,避免修改width
、height
或margin
等触发回流的属性。硬件加速可以通过transform: translateZ(0)
或will-change
属性实现。
使用requestAnimationFrame
代替setTimeout
或setInterval
执行动画循环,确保动画帧率与浏览器刷新率同步,避免卡顿或掉帧。
function animate() { // 动画逻辑 requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
降低动画复杂度
简化关键帧数量,避免过多细节。使用缓动函数(如cubic-bezier
)替代线性动画,提升视觉流畅度。
.element { transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}
避免频繁的JS操作DOM
将动画元素脱离文档流(如position: absolute
),减少布局计算。批量处理DOM修改,或使用文档片段(DocumentFragment
)集中更新。
优化SVG和Canvas动画
对SVG动画使用transform
而非直接修改路径属性。Canvas动画中,离屏渲染复杂图形以减少每帧绘制开销。
10.监控小程序性能
使用微信开发者工具的性能面板
微信开发者工具内置了性能分析工具,可以实时监控小程序运行时的各项指标。在开发者工具中点击「调试器」→「性能」面板,可查看渲染性能、脚本执行时间、内存占用等数据。例如,通过「FPS」曲线可直观判断页面是否存在卡顿。
接入微信官方性能监控API
小程序提供了性能相关的API,如wx.reportPerformance()
用于上报自定义性能数据,wx.getPerformance()
获取性能管理器实例。示例代码:
// 上报自定义性能指标
wx.reportPerformance(1001, 1, 'load_time', 1500);// 获取性能数据
const performance = wx.getPerformance();
const observer = performance.createObserver((entryList) => {entryList.getEntries().forEach(entry => {console.log(entry.name, entry.duration);});
});
observer.observe({ entryTypes: ['render', 'script'] });
埋点关键性能指标
关键指标包括:页面加载时间(onLoad到onReady)、首次渲染时间(First Render)、接口请求耗时、动画帧率等。通过wx.getSystemInfoSync()
可获取设备信息辅助分析性能差异。示例:
Page({onReady() {const start = Date.now();// 模拟业务逻辑setTimeout(() => {const cost = Date.now() - start;wx.reportAnalytics('page_ready_time', { duration: cost });}, 1000);}
});
服务器端日志分析
将性能数据通过HTTP请求上报至自有服务器,结合日志分析工具(如ELK)进行聚合分析。示例上报代码:
function reportToServer(metricName, value) {wx.request({url: 'https://your-domain.com/performance',method: 'POST',data: { appId: 'your_appid',metric: metricName,value: value,timestamp: Date.now()}});
}
典型性能优化示例
首屏加载优化
- 使用分包加载机制减少初始包体积
- 对图片资源使用CDN并压缩,格式优先选择WebP
- 提前发起数据请求,在
onLoad
阶段并行请求接口
渲染性能优化
- 避免在
setData
中传递过大数据,示例:
// 反例:一次性更新大量数据
this.setData({ list: hugeArray });// 正例:分批更新
this.setData({ 'list[0]': chunk });
- 使用
virtual-list
组件优化长列表渲染
内存管理
- 及时清理定时器:在
onUnload
中清除setInterval
- 对全局事件监听进行解绑