uni-app 小程序分包加载
核心知识点
1. 分包加载原理
小程序分包加载是一种优化小程序性能的技术,主要原理包括:
- 主包:包含默认启动页面、TabBar 页面以及所有分包都需要用到的公共资源和代码
- 分包:根据功能模块划分为不同的包,按需加载
- 加载机制:首次启动只加载主包,进入分包页面时才加载对应分包
- 体积限制:主包体积不超过 2MB,单个分包体积不超过 2MB,总体积不超过 20MB
2. 分包配置方法
在 uni-app 中,分包配置主要通过 pages.json 文件实现:
- subPackages:配置分包路径和页面
- preloadRule:配置分包预加载规则
- independent:配置独立分包
3. 分包优化策略
有效的分包优化策略包括:
- 合理划分模块:根据功能相关性和使用频率划分分包
- 公共资源管理:将公共组件和资源放在主包中
- 预加载优化:根据用户行为预加载可能需要的分包
- 体积监控:定期检查各包体积,避免超限
实用案例分析
案例:实现小程序分包加载
1. 项目结构设计
假设我们有一个电商应用,包含以下功能模块:
- 主包:首页、分类、购物车、我的(TabBar 页面)
- 分包 1:商品详情、订单相关功能
- 分包 2:用户中心、设置相关功能
- 分包 3:活动、营销相关功能
2. 分包配置实现
pages.json 配置文件:
{
"pages": [
// 主包页面
{"path": "pages/index/index", "style": {}}, // 首页
{"path": "pages/category/category", "style": {}}, // 分类
{"path": "pages/cart/cart", "style": {}}, // 购物车
{"path": "pages/user/user", "style": {}} // 我的
],
"subPackages": [
// 分包 1:商品与订单
{
"root": "pages/goods",
"pages": [
{"path": "detail/detail", "style": {}},
{"path": "order/confirm", "style": {}},
{"path": "order/list", "style": {}},
{"path": "order/detail", "style": {}}
]
},
// 分包 2:用户中心
{
"root": "pages/profile",
"pages": [
{"path": "settings/settings", "style": {}},
{"path": "address/list", "style": {}},
{"path": "address/edit", "style": {}},
{"path": "coupon/list", "style": {}}
]
},
// 分包 3:活动营销
{
"root": "pages/activity",
"pages": [
{"path": "seckill/seckill", "style": {}},
{"path": "group/group", "style": {}},
{"path": "coupon-center/coupon-center", "style": {}}
]
}
],
"preloadRule": {
// 进入首页时预加载商品分包
"pages/index/index": {
"network": "all",
"packages": ["pages/goods"]
},
// 进入分类页时预加载商品分包
"pages/category/category": {
"network": "all",
"packages": ["pages/goods"]
},
// 进入购物车时预加载订单分包
"pages/cart/cart": {
"network": "all",
"packages": ["pages/goods"]
},
// 进入我的页面时预加载用户中心分包
"pages/user/user": {
"network": "all",
"packages": ["pages/profile"]
}
},
"tabBar": {
"color": "#999",
"selectedColor": "#007AFF",
"borderStyle": "black",
"backgroundColor": "#fafafa",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "static/tabbar/category.png",
"selectedIconPath": "static/tabbar/category-active.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart-active.png"
},
{
"pagePath": "pages/user/user",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "电商应用",
"navigationBarBackgroundColor": "#f8f8f8",
"backgroundColor": "#f8f8f8"
}
}3. 分包页面跳转实现
首页跳转到商品详情 (pages/index/index.vue):
<template>
<view class="index-container">
<view class="goods-list">
<view v-for="goods in goodsList" :key="goods.id" class="goods-item">
<image :src="goods.image" class="goods-image"></image>
<view class="goods-info">
<text class="goods-name">{{ goods.name }}</text>
<text class="goods-price">¥{{ goods.price }}</text>
<button @click="goToDetail(goods.id)" type="primary" size="mini">查看详情</button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
goodsList: [
{ id: 1, name: '商品1', price: 99, image: 'https://via.placeholder.com/200' },
{ id: 2, name: '商品2', price: 199, image: 'https://via.placeholder.com/200' },
{ id: 3, name: '商品3', price: 299, image: 'https://via.placeholder.com/200' }
]
};
},
methods: {
goToDetail(goodsId) {
uni.navigateTo({
url: '/pages/goods/detail/detail?id=' + goodsId
});
}
}
};
</script>
<style scoped>
.index-container {
padding: 20rpx;
}
.goods-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.goods-item {
display: flex;
background-color: #fff;
border-radius: 10rpx;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.goods-image {
width: 160rpx;
height: 160rpx;
border-radius: 5rpx;
}
.goods-info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.goods-name {
font-size: 32rpx;
font-weight: 500;
margin-bottom: 10rpx;
}
.goods-price {
font-size: 36rpx;
color: #ff4d4f;
margin-bottom: 10rpx;
}
</style>商品详情页 (pages/goods/detail/detail.vue):
<template>
<view class="detail-container">
<view class="goods-image">
<image :src="goodsDetail.image" class="image"></image>
</view>
<view class="goods-info">
<text class="goods-name">{{ goodsDetail.name }}</text>
<text class="goods-price">¥{{ goodsDetail.price }}</text>
<text class="goods-desc">{{ goodsDetail.description }}</text>
</view>
<view class="action-bar">
<button @click="addToCart" type="default">加入购物车</button>
<button @click="buyNow" type="primary">立即购买</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
goodsDetail: {
id: '',
name: '',
price: 0,
image: '',
description: ''
}
};
},
onLoad(options) {
this.getGoodsDetail(options.id);
},
methods: {
getGoodsDetail(id) {
// 模拟获取商品详情
this.goodsDetail = {
id: id,
name: '商品' + id,
price: 99 * id,
image: 'https://via.placeholder.com/400',
description: '这是商品' + id + '的详细描述,包含商品的各种参数和特点。'
};
},
addToCart() {
uni.showToast({ title: '已加入购物车', icon: 'success' });
},
buyNow() {
uni.navigateTo({
url: '/pages/goods/order/confirm?id=' + this.goodsDetail.id
});
}
}
};
</script>
<style scoped>
.detail-container {
padding-bottom: 100rpx;
}
.goods-image {
width: 100%;
height: 500rpx;
background-color: #f5f5f5;
}
.image {
width: 100%;
height: 100%;
}
.goods-info {
padding: 20rpx;
background-color: #fff;
margin-bottom: 20rpx;
}
.goods-name {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.goods-price {
font-size: 40rpx;
color: #ff4d4f;
margin-bottom: 20rpx;
}
.goods-desc {
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
background-color: #fff;
padding: 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.action-bar button {
flex: 1;
margin: 0 10rpx;
}
</style>订单确认页 (pages/goods/order/confirm.vue):
<template>
<view class="confirm-container">
<view class="order-info">
<text class="title">订单确认</text>
<view class="goods-item">
<text class="goods-name">商品{{ goodsId }}</text>
<text class="goods-price">¥{{ price }}</text>
</view>
<view class="address-info">
<text class="label">收货地址</text>
<view class="address-detail">
<text>请选择收货地址</text>
<uni-icon type="arrowright" size="24"></uni-icon>
</view>
</view>
<view class="payment-info">
<text class="label">支付方式</text>
<view class="payment-options">
<view class="option" :class="{ active: paymentMethod === 'wechat' }" @click="paymentMethod = 'wechat'">
<text>微信支付</text>
<uni-icon v-if="paymentMethod === 'wechat'" type="checkbox-filled" color="#007AFF"></uni-icon>
<uni-icon v-else type="checkbox" color="#999"></uni-icon>
</view>
<view class="option" :class="{ active: paymentMethod === 'alipay' }" @click="paymentMethod = 'alipay'">
<text>支付宝</text>
<uni-icon v-if="paymentMethod === 'alipay'" type="checkbox-filled" color="#007AFF"></uni-icon>
<uni-icon v-else type="checkbox" color="#999"></uni-icon>
</view>
</view>
</view>
</view>
<view class="total-bar">
<text class="total-label">合计:</text>
<text class="total-price">¥{{ price }}</text>
<button @click="submitOrder" type="primary">提交订单</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
goodsId: '',
price: 0,
paymentMethod: 'wechat'
};
},
onLoad(options) {
this.goodsId = options.id;
this.price = 99 * this.goodsId;
},
methods: {
submitOrder() {
uni.showToast({ title: '订单提交成功', icon: 'success' });
setTimeout(() => {
uni.navigateTo({
url: '/pages/goods/order/list/list'
});
}, 1500);
}
}
};
</script>
<style scoped>
.confirm-container {
padding-bottom: 120rpx;
}
.order-info {
background-color: #fff;
margin-bottom: 20rpx;
padding: 20rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
margin-bottom: 30rpx;
}
.goods-item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid #eee;
}
.goods-name {
font-size: 32rpx;
}
.goods-price {
font-size: 32rpx;
color: #ff4d4f;
}
.address-info,
.payment-info {
margin-top: 30rpx;
}
.label {
font-size: 28rpx;
color: #666;
margin-bottom: 15rpx;
display: block;
}
.address-detail {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 5rpx;
}
.payment-options {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
background-color: #f5f5f5;
border-radius: 5rpx;
}
.option.active {
background-color: #e6f7ff;
}
.total-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
background-color: #fff;
padding: 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.total-label {
font-size: 32rpx;
}
.total-price {
font-size: 36rpx;
color: #ff4d4f;
font-weight: bold;
margin: 0 20rpx;
flex: 1;
}
.total-bar button {
flex: 0 0 200rpx;
}
</style>4. 独立分包配置
独立分包是一种特殊的分包,可以独立于主包运行,主要用于:
- 快速启动:用户可以直接进入独立分包,无需加载主包
- 减少主包体积:将一些不依赖主包的功能模块配置为独立分包
pages.json 中配置独立分包:
{
"subPackages": [
{
"root": "pages/independent",
"pages": [
{"path": "login/login", "style": {}}
],
"independent": true
}
]
}独立分包登录页面 (pages/independent/login/login.vue):
<template>
<view class="login-container">
<view class="login-form">
<image src="../../static/logo.png" class="logo"></image>
<view class="form-item">
<input v-model="form.username" placeholder="请输入用户名" />
</view>
<view class="form-item">
<input v-model="form.password" type="password" placeholder="请输入密码" />
</view>
<button @click="login" type="primary">登录</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
form: {
username: '',
password: ''
}
};
},
methods: {
login() {
// 登录逻辑
uni.showToast({ title: '登录成功', icon: 'success' });
setTimeout(() => {
// 登录成功后跳转到主包页面
uni.switchTab({ url: '/pages/index/index' });
}, 1500);
}
}
};
</script>
<style scoped>
.login-container {
padding: 40rpx;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
}
.login-form {
width: 100%;
max-width: 500rpx;
background-color: #fff;
padding: 40rpx;
border-radius: 10rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.logo {
width: 160rpx;
height: 160rpx;
margin: 0 auto 40rpx;
display: block;
}
.form-item {
margin-bottom: 30rpx;
}
.form-item input {
border: 1rpx solid #ddd;
border-radius: 5rpx;
padding: 20rpx;
font-size: 32rpx;
}
.login-form button {
margin-top: 20rpx;
}
</style>学习目标
通过本章节的学习,你应该能够:
- 理解分包加载原理:掌握小程序分包加载的工作机制和体积限制
- 掌握分包配置方法:能够在 uni-app 中正确配置分包和预加载规则
- 实现分包优化:根据项目特点合理划分分包,优化应用性能
- 使用独立分包:了解独立分包的使用场景和配置方法
- 监控分包体积:定期检查分包体积,避免超限问题
总结
小程序分包加载是解决小程序体积过大、提升启动速度的有效方法。通过合理划分主包和分包,按需加载资源,可以显著提升用户体验。
在实际项目中,我们需要注意以下几点:
- 合理划分模块:根据功能相关性和使用频率划分分包,避免分包过大或过小
- 优化预加载策略:根据用户行为路径配置预加载规则,提升页面切换速度
- 公共资源管理:将公共组件和资源放在主包中,避免重复加载
- 独立分包使用:对于一些不依赖主包的功能,如登录页面,可以配置为独立分包
- 体积监控:定期检查各包体积,及时优化,避免超限
通过本章节的学习和实践,你应该能够熟练掌握 uni-app 小程序分包加载的配置和优化方法,为用户提供更快速、更流畅的应用体验。