目录
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 = true
const 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
- 对全局事件监听进行解绑