微信小程序电商实战 09:性能优化——分包加载、虚拟列表与图片懒加载
微信小程序电商实战 01 技术选型 | 02 脚手架搭建 | 03 NutUI 配置 | 04 云开发环境 | 05 商品模块 | 06 购物车与订单 | 07 微信支付 | 08 用户中心 | 09 性能优化 ← 本文 | 10 上线部署
# 小程序电商性能优化
## 包体积优化
### 分包加载
- 主包 ≤ 2MB(tabBar页面)
- 子包:商品详情/订单/用户
### NutUI 按需加载
- babel-plugin-import
- 只打入用到的组件
### 图片不入包
- 云存储 URL,不放 assets
## 列表性能
### 虚拟列表
- @tarojs/components VirtualList
- 商品列表 100+ 条
### 分页 + 无限滚动
- 每页 10 条
- scrolltolower 触发
## 图片优化
### 懒加载(lazy-load)
- nut-image 属性
### 格式优化
- 云存储支持 WebP
- 缩略图参数
### 骨架屏
- 加载期间占位
## 启动优化
### 数据预拉取
- wx.setBackgroundFetchToken
### 周期性更新
- 首页分类数据缓存
## 渲染优化
### setData 最小化
- 避免整个列表 setData
### 组件化拆分
- 减少单页节点数
一、分包加载(最重要)
微信小程序主包限制 2MB,整个包限制 20MB。电商项目 NutUI + Taro 运行时已接近限制,必须做分包。
分包策略
主包(≤2MB):
pages/index/ # 首页(tabBar)
pages/cart/ # 购物车(tabBar)
pages/order/list # 订单列表(tabBar)
pages/user/index # 用户中心(tabBar)
子包 A — 商品:
pages/product/ # 商品详情
pages/index/list # 商品列表
子包 B — 交易:
pages/order/confirm # 订单确认
pages/order/pay # 支付页
pages/order/result # 支付结果
子包 C — 用户管理:
pages/user/address
pages/user/address-edit
pages/user/about
在 app.config.ts 配置分包
// src/app.config.ts
export default defineAppConfig({
pages: [
'pages/index/index',
'pages/cart/index',
'pages/order/list',
'pages/user/index',
],
subPackages: [
{
root: 'packageProduct',
pages: [
'pages/product/index',
'pages/index/list',
],
},
{
root: 'packageOrder',
pages: [
'pages/order/confirm',
'pages/order/pay',
'pages/order/result',
],
},
{
root: 'packageUser',
pages: [
'pages/user/address',
'pages/user/address-edit',
'pages/user/about',
],
},
],
preloadRule: {
'pages/index/index': {
network: 'all',
packages: ['packageProduct'], // 进首页时预加载商品子包
},
},
})
跳转子包页面时路径前加子包根目录:
// 跳转到子包内的页面
Taro.navigateTo({ url: '/packageProduct/pages/product/index?id=xxx' })
二、虚拟列表(商品列表 100+ 条)
当商品列表超过 100 条时,直接渲染会导致内存占用飙升、滚动卡顿。使用 Taro 内置的 VirtualList 只渲染可视区域内的节点:
<!-- 商品虚拟列表 -->
<template>
<virtual-list
class="product-virtual-list"
:height="windowHeight"
:item-count="products.length"
:item-size="240"
:render-count="10"
>
<template #default="{ index, style }">
<view :style="style">
<ProductCard :product="products[index]" @open-sku="openSkuPanel" />
</view>
</template>
</virtual-list>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import { VirtualList } from '@tarojs/components'
import ProductCard from '@/components/ProductCard.vue'
const windowHeight = Taro.getSystemInfoSync().windowHeight
</script>
适用场景:商品列表超过 80 条时启用。50 条以内的普通
v-for性能已足够,不必过早优化。
三、图片优化
1. 云存储图片格式转换
云存储 URL 支持通过参数获取 WebP 格式和缩略图,大幅减小图片体积:
// src/utils/image.ts
// 商品列表缩略图:转 WebP,400px 宽
export function getThumbnail(cloudUrl: string, width = 400): string {
if (!cloudUrl.startsWith('cloud://')) return cloudUrl
// 腾讯云 COS 图片处理参数
return `${cloudUrl}?imageView2/2/w/${width}/format/webp/q/80`
}
// 商品详情大图:原始尺寸但 WebP
export function getDetailImage(cloudUrl: string): string {
if (!cloudUrl.startsWith('cloud://')) return cloudUrl
return `${cloudUrl}?imageMogr2/format/webp/quality/85`
}
2. 懒加载 + 骨架屏
<!-- 带骨架屏的商品卡片 -->
<template>
<view class="product-card">
<view v-if="!imageLoaded" class="skeleton-image" />
<nut-image
:src="getThumbnail(product.coverImage)"
width="100%"
height="180px"
fit="cover"
lazy-load
@load="imageLoaded = true"
/>
<!-- ... -->
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { getThumbnail } from '@/utils/image'
const imageLoaded = ref(false)
</script>
<style lang="scss">
.skeleton-image {
width: 100%;
height: 180px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 400% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 100% 0; }
100% { background-position: -100% 0; }
}
</style>
四、setData 优化
Taro 底层会把数据变化转为小程序的 setData,频繁的大量 setData 是性能杀手。
列表更新时只追加,不替换整个数组:
// ❌ 错误:每次加载都替换整个列表
products.value = [...products.value, ...newItems] // 触发全量 setData
// ✅ 正确:使用 push 追加(Taro 会生成差量 setData)
products.value.push(...newItems)
高频更新场景(如搜索输入)加防抖:
import { debounce } from 'lodash-es'
const search = debounce(async (keyword: string) => {
const res = await searchProducts(keyword)
products.value = res.list
}, 300)
五、首屏数据预加载
利用微信的「数据预拉取」在小程序冷启动时提前请求数据:
// app.ts — onLaunch 阶段并行初始化
onLaunch() {
initCloud()
// 并行请求首屏数据,不阻塞 UI 渲染
Promise.all([
userStore.silentLogin(),
prefetchHomeData(),
])
}
async function prefetchHomeData() {
// 首页分类和推荐数据写入本地缓存
const [cats, products] = await Promise.all([
getCategories(),
getProductList({ page: 1, pageSize: 10 }),
])
Taro.setStorageSync('home_categories', cats)
Taro.setStorageSync('home_products', products)
Taro.setStorageSync('home_cache_time', Date.now())
}
首页读取时优先用缓存,5 分钟内不重复请求:
onMounted(async () => {
const cacheTime = Taro.getStorageSync('home_cache_time')
const isFresh = Date.now() - cacheTime < 5 * 60 * 1000
if (isFresh) {
categories.value = Taro.getStorageSync('home_categories')
products.value = Taro.getStorageSync('home_products')?.list ?? []
} else {
await loadHomeData()
}
})
微信小程序电商实战 01 技术选型 | 02 脚手架搭建 | 03 NutUI 配置 | 04 云开发环境 | 05 商品模块 | 06 购物车与订单 | 07 微信支付 | 08 用户中心 | 09 性能优化 ← 本文 | 10 上线部署
实操清单
- 在
app.config.ts配置分包:主包只留 4 个 tabBar 页面,其余移入子包 -
pnpm build:weapp后查看dist/目录,验证主包大小 ≤ 2MB - 配置
preloadRule,进首页时预加载商品子包 - 确认
babel.config.js的按需加载配置已生效(对比开启前后包体积) - 在商品数量超过 50 条的列表页引入
VirtualList,对比滚动流畅度 - 实现
getThumbnail()工具函数,对比原始图和转 WebP 的加载时间 - 为商品卡片图片加入骨架屏占位动画
- 将列表追加从
value = [...old, ...new]改为value.push(...new) - 对搜索输入加 300ms 防抖
- 实现首页数据预拉取 + 5 分钟缓存,验证刷新首页时不重复请求接口