小程序开发
前言
小程序(Mini Program)是一种无需下载安装即可使用的应用形态,由微信于 2017 年 1 月率先推出。用户通过"扫一扫"或"搜一搜"即可打开应用,体现了"用完即走"的理念。经过数年发展,小程序已从微信生态扩展到支付宝、百度、字节跳动、QQ 等多家平台,形成了庞大的跨平台开发生态。
本文将从小程序的原生开发入手,逐步深入到 Taro、uni-app 等跨平台框架,并覆盖云开发、支付接入、登录体系、性能优化等核心主题,帮助开发者全面掌握小程序开发技能。
一、微信小程序原生开发
1.1 双线程架构
微信小程序的运行环境采用独特的双线程架构:
- 渲染层(WebView 线程):负责页面渲染,运行 WXML 和 WXSS。一个小程序可以有多个页面,每个页面运行在独立的 WebView 中。
- 逻辑层(JsCore 线程):负责业务逻辑,运行 JavaScript 代码。逻辑层无法直接操作 DOM,只能通过数据驱动方式更新界面。
两个线程通过微信原生客户端进行通信:逻辑层通过 setData 将数据发送到渲染层,渲染层通过事件系统将用户操作回传给逻辑层。这种架构避免了渲染被 JavaScript 长时间计算阻塞,但同时也带来了数据传输的性能开销。
双线程架构的通信流程如下:
- 用户在界面上触发事件(点击、滑动等)
- 渲染层将事件传递给逻辑层
- 逻辑层处理事件并调用
setData更新数据 - 数据经由 Native 传递到渲染层
- 渲染层根据新数据重新渲染视图
1.2 WXML 模板语言
WXML(WeiXin Markup Language)是微信小程序框架设计的一套标签语言,类似 HTML,但进行了针对性定制:
- 数据绑定:使用 Mustache 语法
{% raw %}{% endraw %}将数据绑定到视图 - 条件渲染:
wx:if、wx:elif、wx:else - 列表渲染:
wx:for、wx:for-item、wx:key - 模板引用:
<import>和<include> - 事件系统:以
bind/catch前缀绑定事件,支持事件冒泡和捕获
WXML 没有 HTML 中完整的 DOM 树能力,渲染性能优于传统 Web 页面。一个典型的 WXML 片段如下:
<view class="container">
<block wx:for="{{items}}" wx:key="id">
<view class="item" bindtap="onItemTap" data-id="{{item.id}}">
<text>{{item.name}}</text>
<text class="price">¥{{item.price}}</text>
</view>
</block>
</view>1.3 WXSS 样式系统
WXSS(WeiXin Style Sheets)是类 CSS 的样式语言,在 CSS 基础上做了扩展和限制:
- 尺寸单位 rpx:根据屏幕宽度自适应,设计稿以 750rpx 为基准。例如 iPhone 6 上 1rpx = 0.5px,iPhone 6 Plus 上 1rpx ≈ 0.552px。
- 不支持的选择器:不支持通配符
*、伪类:visited、媒体查询等部分 CSS 特性。 - @import 引入:使用
@import语句引入外部样式文件。 - 内联样式:支持
style属性动态绑定样式。
.container {
display: flex;
flex-direction: column;
padding: 30rpx;
}
.item {
display: flex;
justify-content: space-between;
padding: 20rpx 0;
border-bottom: 1rpx solid #e5e5e5;
}
.price {
color: #fa5151;
font-size: 32rpx;
font-weight: 600;
}1.4 逻辑层 JavaScript
小程序的逻辑层运行在 JsCore 环境中(iOS 为 JavaScriptCore,Android 为 V8),与浏览器环境有所不同:
- 无 BOM/DOM API:不能直接操作
window、document对象 - 模块化:支持 CommonJS 规范的
require和module.exports - App/Page 构造器:通过
App()注册小程序,Page()注册页面 - 生命周期:页面有
onLoad、onShow、onReady、onHide、onUnload等生命周期钩子 - API 调用:所有微信能力通过
wx命名空间调用,如wx.request、wx.getStorage等
Page({
data: {
items: [],
loading: false
},
onLoad(options) {
this.loadData();
},
loadData() {
this.setData({ loading: true });
wx.request({
url: 'https://api.example.com/items',
success: (res) => {
this.setData({ items: res.data });
},
complete: () => {
this.setData({ loading: false });
}
});
}
});1.5 组件系统
微信小程序提供了丰富的内置组件,涵盖视图容器、基础内容、表单、导航、媒体、地图、画布等类别:
- 视图容器:
view、scroll-view、swiper、movable-view - 基础内容:
icon、text、rich-text、progress - 表单组件:
button、input、checkbox、radio、picker、slider - 导航组件:
navigator - 媒体组件:
image、video、audio、camera - 开放能力:
open-data、web-view、ad
自定义组件(Component)也是小程序的重要组成部分,开发者可以将 UI 和逻辑封装为可复用的组件:
Component({
properties: {
title: { type: String, value: '' },
count: { type: Number, value: 0 }
},
data: {},
methods: {
onTap() {
this.triggerEvent('tap', { count: this.data.count });
}
}
});1.6 API 能力
微信小程序对外开放了大量原生 API,覆盖了手机硬件的方方面面:
- 网络:
wx.request、wx.uploadFile、wx.downloadFile、wx.connectSocket - 存储:
wx.setStorageSync、wx.getStorageSync、wx.removeStorage - 位置:
wx.getLocation、wx.chooseLocation、wx.openLocation - 设备:
wx.getSystemInfo、wx.getNetworkType、wx.scanCode - 媒体:
wx.chooseImage、wx.previewImage、wx.getRecorderManager - 支付:
wx.requestPayment - 登录:
wx.login、wx.checkSession - 分享:
wx.shareAppMessage、wx.updateShareMenu - 消息:
wx.getSetting、wx.requestSubscribeMessage
这些 API 的使用需要在小程序管理后台申请对应权限,且在调用前需要用户授权。
二、Taro 开发小程序
2.1 概述
Taro 是由京东凹凸实验室推出的开源跨平台框架,支持使用 React 语法编写代码,然后编译到微信、支付宝、百度、字节跳动、QQ 等多个平台的小程序,以及 H5 和 React Native。
Taro 的核心思路是编译时转换 + 运行时适配,在编译阶段将 React 代码转换为目标平台的代码,在运行时通过适配层抹平平台差异。
2.2 React 语法编译
Taro 3.x 完全基于 React 语法,开发者可以使用 JSX、Hooks、Context 等 React 特性:
import { View, Text, Button } from '@tarojs/components';
import { useState, useEffect } from '@tarojs/taro';
export default function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
Taro.request({ url: '/api/products' }).then(res => {
setProducts(res.data);
});
}, []);
return (
<View className='container'>
{products.map(item => (
<View key={item.id} className='item'>
<Text>{item.name}</Text>
<Text className='price'>¥{item.price}</Text>
</View>
))}
</View>
);
}Taro 在编译时做了以下工作:
- JSX 转换:将 JSX 语法解析为抽象语法树(AST),然后转换为目标平台模板语言
- 样式处理:处理 CSS/SCSS/Less,转换为目标平台兼容的样式
- 组件映射:将
@tarojs/components中的组件映射为目标平台的内置组件 - API 适配:将
Taro.*API 调用映射为不同平台对应的 API
2.3 跨平台适配
Taro 通过以下机制实现跨平台适配:
条件编译:开发者可以在同一代码库中为不同平台编写特定逻辑:
// 仅在微信小程序中编译
#ifdef WEAPP
Taro.showToast({ title: '微信小程序' })
#endif
// 仅在支付宝小程序中编译
#ifdef ALIPAY
Taro.showToast({ title: '支付宝小程序' })
#endif平台配置文件:config/index.js 中可以按平台配置构建参数:
const config = {
// 通用配置
defineConstants: {},
// 微信小程序配置
mini: {
weapp: {
compile: {
exclude: []
}
}
},
// H5 配置
h5: {}
};2.4 运行时转换
Taro 3.x 采用重运行时的架构,在目标平台上模拟 React 运行时环境:
- 在微信小程序中,Taro 会创建一个根组件作为运行时容器
- 通过 Virtual DOM 对比算法(类似 React DOM)计算出差异
- 调用小程序的
setData更新视图
这种方式使得开发者可以几乎使用完整的 React 特性,但也带来了一些性能开销。
2.5 配置转换
Taro 的配置系统分为三个层次:
- 全局配置(
app.config.js):对应小程序的app.json - 页面配置(每个页面的
.config.js):对应页面的.json配置 - 构建配置(
config/index.js):构建工具配置
// app.config.js
export default {
pages: ['pages/index/index', 'pages/detail/index'],
window: {
navigationBarTitleText: '我的小程序',
navigationBarBackgroundColor: '#07c160'
},
tabBar: {
list: [
{ pagePath: 'pages/index/index', text: '首页' },
{ pagePath: 'pages/profile/index', text: '我的' }
]
}
};三、uni-app 开发小程序
3.1 概述
uni-app 是由 DCloud 推出的跨平台框架,基于 Vue.js 语法,一套代码可发布到 iOS、Android、Web 以及各家小程序平台。与 Taro 不同的是,uni-app 天然使用 Vue 响应式体系,对于熟悉 Vue 的开发者更加友好。
3.2 Vue 语法开发
uni-app 使用 Vue 单文件组件(SFC)语法,开发者可以使用 v-model、v-for、v-if、计算属性、侦听器等 Vue 特性:
<template>
<view class="container">
<view class="header">
<text class="title">{{ title }}</text>
<input v-model="searchText" placeholder="搜索..." />
</view>
<view v-for="item in filteredList" :key="item.id" class="item" @click="goDetail(item)">
<image :src="item.image" mode="aspectFill" />
<view class="info">
<text class="name">{{ item.name }}</text>
<text class="price">¥{{ item.price }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: '商品列表',
searchText: '',
list: []
}
},
computed: {
filteredList() {
return this.list.filter(item =>
item.name.includes(this.searchText)
)
}
},
onLoad() {
this.loadData()
},
methods: {
async loadData() {
const res = await uni.request({ url: '/api/list' })
this.list = res.data
},
goDetail(item) {
uni.navigateTo({ url: `/pages/detail?id=${item.id}` })
}
}
}
</script>3.3 条件编译
uni-app 使用条件编译指令来实现跨平台差异化代码,以 #ifdef 和 #ifndef 为标记:
<!-- #ifdef MP-WEIXIN -->
<view>仅微信小程序显示</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view>仅 App 端显示</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view>非 H5 平台显示</view>
<!-- #endif -->样式和 JavaScript 代码同样支持条件编译:
/* #ifdef MP-WEIXIN */
.container {
padding: 30rpx;
}
/* #endif */// #ifdef H5
document.title = 'uni-app'
// #endif3.4 发布多渠道
uni-app 支持一键发布到多个渠道:
| 渠道 | 命令 | 说明 |
|---|---|---|
| 微信小程序 | uni build --platform mp-weixin | 在微信开发者工具中打开 |
| 支付宝小程序 | uni build --platform mp-alipay | 使用支付宝 IDE 预览 |
| 字节跳动小程序 | uni build --platform mp-toutiao | 使用字节 IDE 预览 |
| H5 | uni build --platform h5 | 普通 Web 应用 |
| App | uni build --platform app | 打包为 Android/iOS |
每个平台构建后生成独立的目录,互不干扰,开发者可以在各自平台的管理后台提交审核。
3.5 Taro 与 uni-app 对比
| 对比维度 | Taro | uni-app |
|---|---|---|
| 基础语法 | React(JSX + Hooks) | Vue(模板 + 选项式/组合式 API) |
| 开发团队 | 京东凹凸实验室 | DCloud |
| 架构思路 | 编译时 + 运行时(重运行时) | 编译时转换 + 运行时适配 |
| 跨平台能力 | 微信/支付宝/百度/字节/QQ/H5/RN | 微信/支付宝/百度/字节/QQ/H5/App |
| 状态管理 | Redux、MobX 等 React 生态 | Vuex、Pinia |
| 路由系统 | React Router 风格 | 类 Vue Router,uni.navigateTo |
| 社区生态 | 插件市场较丰富 | 插件市场非常丰富 |
| TypeScript 支持 | 原生支持 | 良好支持 |
| 性能表现 | 运行时开销略大,大列表需优化 | 编译优化较好,性能接近原生 |
| 学习曲线 | 需 React 基础 | 需 Vue 基础,上手更快 |
| 适用场景 | 团队以 React 技术栈为主 | 团队以 Vue 技术栈为主 |
选择 Taro 还是 uni-app,主要取决于团队的技术栈偏好。React 团队倾向 Taro,Vue 团队倾向 uni-app,两者在生产环境中都有广泛应用和成熟案例。
四、云开发
4.1 云开发概述
微信云开发(Tencent Cloud Base)是微信团队联合腾讯云推出的后端一体化解决方案,开发者无需搭建服务器,即可使用云函数、云数据库、云存储等能力。云开发与小程序天然集成,极大地降低了小程序的开发门槛。
云开发的核心特性:
- 免运维:无需管理服务器,自动扩缩容
- 按量付费:只有使用才付费,小成本起步
- 原生接入:在小程序端通过
wx.cloud直接调用 - 安全可靠:数据存储于腾讯云,多副本备份
4.2 云函数
云函数是运行在 Node.js 环境中的代码片段,用于处理后端逻辑:
// 云函数:getProducts
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { page = 1, pageSize = 10 } = event
const countResult = await db.collection('products').count()
const total = countResult.total
const { data } = await db.collection('products')
.skip((page - 1) * pageSize)
.limit(pageSize)
.orderBy('createTime', 'desc')
.get()
return {
data,
total,
page,
pageSize
}
}小程序端调用:
wx.cloud.callFunction({
name: 'getProducts',
data: { page: 1, pageSize: 10 }
}).then(res => {
console.log(res.result)
})云函数支持定时触发、HTTP 触发和事件触发三种方式。
4.3 云数据库
云数据库是基于 MongoDB 的文档型数据库,提供实时数据同步能力:
- 数据结构:以集合(Collection)为单位,文档(Document)为最小存储单元
- 权限控制:支持所有用户可读仅创建者可写、仅创建者可读写、仅管理员可读写等权限级别
- 索引:支持单字段索引、复合索引、地理位置索引
- 聚合操作:支持
match、group、sort、lookup(类似 MongoDB 的联表查询)
const db = wx.cloud.database()
// 查询
db.collection('products').where({
price: db.command.lte(100)
}).orderBy('sales', 'desc').limit(10).get()
// 写入
db.collection('orders').add({
data: {
userId: 'xxx',
total: 299,
status: 'pending',
createTime: db.serverDate()
}
})4.4 云存储
云存储用于存储图片、视频、文件等资源:
- 上传:从小程序端直接上传到云端
- 下载:获取临时下载链接或云文件 ID 直接使用
- CDN 加速:默认使用腾讯云 CDN 加速
- 权限管理:支持公有读/私有写等权限设置
// 上传文件
wx.cloud.uploadFile({
cloudPath: 'products/001.jpg',
filePath: tempFilePath
}).then(res => {
console.log('fileID:', res.fileID)
})
// 获取临时链接
wx.cloud.getTempFileURL({
fileList: ['cloud://xxx']
}).then(res => {
console.log('tempURL:', res.fileList[0].tempFileURL)
})4.5 云调用
云调用允许云函数中直接调用微信的开放 API,无需获取 access_token:
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
exports.main = async (event, context) => {
// 发送订阅消息
await cloud.openapi.subscribeMessage.send({
touser: event.openid,
templateId: 'templateId',
data: {
thing1: { value: '订单已发货' },
date2: { value: '2026-07-18' }
}
})
// 生成小程序码
const result = await cloud.openapi.wxacode.getUnlimited({
scene: 'id=1001',
page: 'pages/index/index'
})
return result
}云调用支持消息推送、模板消息、小程序码、内容安全检测等上百个微信开放 API。
4.6 环境管理
云开发支持多环境管理,通常分为开发环境、测试环境和生产环境:
- 环境隔离:不同环境的数据库、存储、云函数完全隔离
- 环境共享:通过跨环境调用,生产环境可以调用其他环境的资源
- 环境切换:小程序端通过
wx.cloud.init的env参数指定环境
环境管理最佳实践:
- 开发阶段使用开发环境,避免影响线上数据
- 测试环境用于联调和验收
- 生产环境配置较高的配额和性能规格
4.7 费用模型
云开发的计费方式为按量计费,具体包括:
| 计费项 | 免费额度 | 超出单价 |
|---|---|---|
| 云函数调用次数 | 每月 100 万次 | 约 0.0133 元/万次 |
| 云函数资源用量 | 每月 4 万 GBs | 约 0.000111 元/GBs |
| 云数据库读写次数 | 每月 50 万次读 / 30 万次写 | 约 0.01 元/万次 |
| 云数据库存储 | 2 GB | 约 0.07 元/GB/天 |
| 云存储容量 | 10 GB | 约 0.004 元/GB/天 |
| CDN 流量 | 2 GB/月 | 约 0.18 元/GB |
对于个人开发者和小型项目,免费额度通常足够使用。对于中等规模项目,月均费用通常在几十到几百元之间。
五、支付接入
5.1 微信支付流程
微信小程序支付是微信生态内最常用的支付方式,其完整流程如下:
小程序 → 后端服务 → 微信支付 → 后端回调 → 小程序具体步骤:
- 用户在商品详情页点击"立即购买"
- 小程序调用后端接口,后端生成商户订单
- 后端调用微信支付统一下单 API,获取预支付 ID(prepay_id)
- 后端对支付参数进行签名,返回给小程序端
- 小程序调用
wx.requestPayment调起支付界面 - 用户确认支付,输入密码
- 微信支付异步通知后端支付结果
- 后端更新订单状态,通知小程序
5.2 统一下单
后端服务调用微信支付统一下单 API:
// Node.js 示例
const crypto = require('crypto')
async function unifiedOrder(params) {
const { openid, totalFee, outTradeNo, body, spbillCreateIp } = params
const requestParams = {
appid: APPID,
mch_id: MCH_ID,
nonce_str: crypto.randomBytes(16).toString('hex'),
body: body,
out_trade_no: outTradeNo,
total_fee: totalFee, // 单位:分
spbill_create_ip: spbillCreateIp,
notify_url: NOTIFY_URL,
trade_type: 'JSAPI',
openid: openid
}
// 生成签名
requestParams.sign = generateSign(requestParams, API_KEY)
// 发送请求(使用 XML 格式)
const response = await postXml('https://api.mch.weixin.qq.com/pay/unifiedorder', requestParams)
// 返回 prepay_id
return {
prepayId: response.prepay_id,
// 小程序调起支付所需的参数
payParams: generatePayParams(response.prepay_id)
}
}
function generatePayParams(prepayId) {
const params = {
appId: APPID,
timeStamp: String(Math.floor(Date.now() / 1000)),
nonceStr: crypto.randomBytes(16).toString('hex'),
package: `prepay_id=${prepayId}`,
signType: 'HMAC-SHA256'
}
params.paySign = generateSign(params, API_KEY)
return params
}5.3 小程序端调起支付
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success(res) {
// 支付成功
console.log('支付成功', res)
},
fail(err) {
// 支付失败或取消
console.log('支付失败', err)
}
})5.4 支付回调
支付结果通过异步通知的方式发送到商户的后端接口:
// Node.js 回调处理
app.post('/payment/notify', async (req, res) => {
const xml = req.body
const result = parseXml(xml)
// 验证签名
if (!verifySign(result, API_KEY)) {
return res.send(packageXml({ return_code: 'FAIL', return_msg: '签名失败' }))
}
// 检查支付结果
if (result.return_code !== 'SUCCESS' || result.result_code !== 'SUCCESS') {
return res.send(packageXml({ return_code: 'FAIL', return_msg: '支付失败' }))
}
// 更新订单状态
await updateOrderStatus(result.out_trade_no, {
status: 'paid',
transactionId: result.transaction_id,
paidAt: new Date()
})
// 通知微信支付接收成功
res.send(packageXml({ return_code: 'SUCCESS', return_msg: 'OK' }))
})5.5 退款
退款需要调用微信支付退款 API(需要双向证书):
async function refund(orderNo, totalFee, refundFee, reason) {
const params = {
appid: APPID,
mch_id: MCH_ID,
nonce_str: crypto.randomBytes(16).toString('hex'),
out_trade_no: orderNo,
out_refund_no: `REFUND_${orderNo}_${Date.now()}`,
total_fee: totalFee,
refund_fee: refundFee,
refund_desc: reason
}
params.sign = generateSign(params, API_KEY)
// 退款 API 需要使用商户证书
const response = await postXmlWithCert(
'https://api.mch.weixin.qq.com/secapi/pay/refund',
params,
CERT_PATH
)
return response
}5.6 安全签名
微信支付的签名算法是保证支付安全的核心。签名流程如下:
- 将所有请求参数按字典序排序
- 拼接为
key=value&key=value格式 - 末尾拼接商户 API 密钥:
&key=API_KEY - 使用 MD5 或 HMAC-SHA256 计算摘要
- 将摘要转为大写字符串
function generateSign(params, apiKey) {
const sortedKeys = Object.keys(params).sort()
const stringA = sortedKeys
.filter(key => params[key] !== '' && key !== 'sign')
.map(key => `${key}=${params[key]}`)
.join('&')
const stringSignTemp = `${stringA}&key=${apiKey}`
return crypto.createHash('md5').update(stringSignTemp).digest('hex').toUpperCase()
}六、登录体系
6.1 wx.login 与静默授权
微信小程序的登录流程基于 wx.login 接口,该接口可以无感知获取临时 code:
wx.login({
success(res) {
if (res.code) {
// 将 code 发送到后端
wx.request({
url: 'https://api.example.com/login',
data: { code: res.code },
success(response) {
// 保存登录态
wx.setStorageSync('token', response.data.token)
}
})
}
}
})静默授权:wx.login 不需要用户点击确认,是"静默"的。它获取的 code 有效期只有 5 分钟,用于后端换取 openid 和 session_key。这种授权只能获取用户的 openid,无法获取昵称、头像等个人信息。
6.2 用户信息获取
自 2021 年 4 月起,微信调整了用户信息获取策略:
wx.getUserInfo不再弹出授权弹窗,返回默认的匿名信息- 需要使用
wx.getUserProfile获取用户信息(需用户主动点击触发) - 或者使用
<button open-type="getUserInfo">组件
// 推荐方式:使用 <button open-type="getUserInfo">
// WXML:
// <button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">获取头像昵称</button>
Page({
onGetUserInfo(e) {
if (e.detail.userInfo) {
const { nickName, avatarUrl } = e.detail.userInfo
this.setData({ nickName, avatarUrl })
}
}
})6.3 手机号获取
获取用户手机号需要使用 <button open-type="getPhoneNumber"> 组件,且必须经过用户明确授权:
<button open-type="getPhoneNumber" bindgetphonenumber="onGetPhoneNumber">
获取手机号
</button>Page({
onGetPhoneNumber(e) {
if (e.detail.code) {
// 将 code 发送到后端解密获取手机号
wx.request({
url: 'https://api.example.com/decrypt-phone',
data: { code: e.detail.code },
success(res) {
console.log('手机号:', res.data.phoneNumber)
}
})
}
}
})手机号的解密只能在后端进行,需要用到 session_key。流程如下:
- 前端获取
code,传给后端 - 后端调用
code2Session获取session_key - 后端使用
session_key解密手机号密文
6.4 unionId
unionId 是用户在微信开放平台下的唯一标识。如果一个开发者有多个公众号、小程序、移动应用等,unionId 可以关联这些不同应用下的同一用户。
获取 unionId 的方式:
- 调用
code2Session接口(需在开放平台绑定应用) - 通过用户信息加密数据解密得到
- 通过支付通知中的
sub_openid关联
// 后端 code2Session 返回数据
{
"openid": "oXXXX-xxxxx",
"session_key": "xxxxx",
"unionid": "uXXXX-xxxxx", // 仅在绑定了开放平台时返回
"errcode": 0,
"errmsg": "ok"
}6.5 登录态维护
小程序登录态的维护通常采用 token 机制:
// 后端登录接口
app.post('/api/login', async (req, res) => {
const { code } = req.body
// 调用微信接口
const wxRes = await get(`https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${code}&grant_type=authorization_code`)
const { openid, session_key } = wxRes
// 生成自定义 token
const token = jwt.sign(
{ openid, sessionKey: session_key.substring(0, 6) },
JWT_SECRET,
{ expiresIn: '7d' }
)
// 存储 session 到 redis
await redis.set(`session:${openid}`, session_key, 'EX', 7 * 24 * 3600)
res.json({ token })
})前端登录态管理:
class AuthService {
static async checkLogin() {
const token = wx.getStorageSync('token')
if (!token) return false
// 检查 session 是否过期
return new Promise(resolve => {
wx.checkSession({
success: () => resolve(true),
fail: () => {
// session 过期,重新登录
wx.removeStorageSync('token')
resolve(false)
}
})
})
}
static async login() {
const { code } = await wx.login()
const { data } = await wx.request({
url: '/api/login',
method: 'POST',
data: { code }
})
wx.setStorageSync('token', data.token)
return data.token
}
}七、小程序性能优化
7.1 包体积优化
小程序的限制:
- 主包大小不得超过 2 MB
- 整个小程序所有分包大小不得超过 20 MB
优化策略:
代码压缩:开启微信开发者工具的"上传时压缩代码"选项,自动去除注释、空白符并混淆变量名。
图片优化:使用 WebP 格式替代 PNG/JPEG;对图标使用字体图标或 SVG 而非图片;图片使用 CDN 地址而非本地打包。
按需引入:移除未使用的组件和代码,避免整体引入 UI 库:
// 不推荐:整体引入
import { Button, Input, List, Card, Toast } from 'some-ui-lib'
// 推荐:按需引入
import Button from 'some-ui-lib/button'
import Input from 'some-ui-lib/input'使用分包:将非首屏功能拆分到分包中,主包只保留核心首页和公共逻辑。
7.2 分包加载
微信小程序的分包加载机制允许将小程序划分为多个包,在用户访问到对应页面时才下载:
{
"pages": [
"pages/index/index",
"pages/login/login"
],
"subPackages": [
{
"root": "packageA",
"pages": [
"pages/product/detail",
"pages/product/list"
]
},
{
"root": "packageB",
"pages": [
"pages/user/order",
"pages/user/coupon"
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["packageA"]
}
}
}分包预加载:通过 preloadRule 配置,在进入某个页面时预下载其他分包。如上例中,进入首页时预加载 packageA,用户点击商品列表时将秒级打开。
独立分包:独立分包可以脱离主包独立运行,适用于分享页、活动页等场景。独立分包中的页面不需要依赖主包的资源。
7.3 预加载策略
除了分包预加载,还有以下预加载策略:
数据预拉取:使用 wx.getBackgroundFetchData 在后台预拉取数据,用户打开小程序时展示缓存数据。
关键资源预加载:在首页 onLoad 中预加载后续页面需要的图片和 API 数据:
Page({
onLoad() {
// 预加载详情页的图片
wx.previewImage({ urls: this.data.productImages })
}
})预连接:使用 wx.connectSocket 预先建立 WebSocket 长连接,减少后续通信延迟。
7.4 setData 优化
setData 是小程序中最核心但也最容易产生性能问题的 API。优化策略:
减少数据量:只设置变化的部分,而非整个对象:
// 不推荐:设置整个对象
this.setData({
userInfo: {
name: '张三',
age: 25,
...otherFields // 大量未变化的数据
}
})
// 推荐:只设置变化字段
this.setData({
'userInfo.name': '张三'
})降低频率:合并多次 setData 调用:
// 不推荐:多次调用
this.setData({ loading: true })
this.setData({ list: newList })
this.setData({ loading: false })
// 推荐:合并为一次
this.setData({
loading: true,
list: newList
})
// 注意:如果需要 loading 先显示再加载,可以分成两次(使用 setTimeout 间隔)使用 diff:Taro 和 uni-app 内部实现了 Virtual DOM diff 对比,自动只发送变化数据。原生小程序需要手动优化,或使用第三方 diff 库。
大数据列表:对于长列表,使用 recycle-view(可回收列表组件)或分段加载,避免一次性渲染大量节点。
{
"usingComponents": {
"recycle-view": "miniprogram-recycle-view/recycle-view"
}
}7.5 缓存策略
合理使用缓存可以大幅提升小程序的加载速度和用户体验:
Storage 缓存:
// 写入缓存(同步)
wx.setStorageSync('product_cache', {
data: products,
expireAt: Date.now() + 5 * 60 * 1000 // 5 分钟过期
})
// 读取缓存(带过期检查)
function getCache(key) {
const cached = wx.getStorageSync(key)
if (!cached) return null
if (Date.now() > cached.expireAt) {
wx.removeStorageSync(key)
return null
}
return cached.data
}图片缓存:微信小程序自带图片缓存机制,同一 URL 的图片不会重复下载。合理利用 CDN 的缓存策略,设置合适的 Cache-Control 头。
数据缓存分层:
| 层级 | 存储位置 | 容量 | 速度 | 适合场景 |
|---|---|---|---|---|
| L1 | 内存变量 | 小 | 最快 | 当前页面数据 |
| L2 | Storage | 10 MB | 快 | 用户配置、常用列表 |
| L3 | 云数据库 | 不限 | 中 | 全量数据 |
| L4 | CDN | 不限 | 中 | 静态资源 |
智能预拉取:利用用户行为分析,在用户浏览 A 页面时预拉取 B 页面的数据并缓存。例如用户在商品列表页时,预拉取点击率最高的商品详情数据。
结语
小程序生态经过多年的发展,已经从微信单一平台演进为多平台、多框架、多场景的庞大生态。无论是选择微信原生开发、Taro 还是 uni-app,核心在于理解小程序的底层架构和设计理念。
对于团队选型,建议:
- 小团队/个人开发者:优先考虑 uni-app 或原生开发,学习成本低
- React 技术栈团队:优先考虑 Taro
- Vue 技术栈团队:优先考虑 uni-app
- 追求极致性能:使用原生开发
- 需要多平台覆盖:使用 Taro 或 uni-app
在开发实践中,牢记性能优化原则,合理利用云开发降低后端成本,规范接入支付和登录体系,才能构建出体验优秀、稳定可靠的小程序应用。