跳到主要内容

小程序开发指南

小程序是一种轻量级的应用形态,无需下载安装即可使用。本指南涵盖主流小程序平台的开发技术和最佳实践。

📚 目录导航

基础入门

进阶实践

相关技术

小程序生态概览

主流小程序平台

/**
* 小程序平台对比
*/

const miniProgramPlatforms = {
// 微信小程序
wechat: {
name: "微信小程序",
userBase: "12亿+",
framework: "WXML + WXSS + JavaScript",
advantages: ["用户基数大", "生态完善", "支付便捷"],
limitations: ["包体积限制", "审核严格"],
developmentTool: "微信开发者工具",
},

// 支付宝小程序
alipay: {
name: "支付宝小程序",
userBase: "10亿+",
framework: "AXML + ACSS + JavaScript",
advantages: ["金融场景强", "企业服务好"],
limitations: ["用户活跃度相对较低"],
developmentTool: "支付宝开发者工具",
},

// 抖音小程序
douyin: {
name: "抖音小程序",
userBase: "6亿+",
framework: "TTML + TTSS + JavaScript",
advantages: ["年轻用户多", "内容分发能力强"],
limitations: ["生态相对较新"],
developmentTool: "抖音开发者工具",
},

// 百度智能小程序
baidu: {
name: "百度智能小程序",
userBase: "5亿+",
framework: "Swan + CSS + JavaScript",
advantages: ["搜索流量", "AI能力"],
limitations: ["用户粘性较低"],
developmentTool: "百度开发者工具",
},

// QQ小程序
qq: {
name: "QQ小程序",
userBase: "8亿+",
framework: "QML + QSS + JavaScript",
advantages: ["年轻用户群体", "社交属性强"],
limitations: ["相对小众"],
developmentTool: "QQ开发者工具",
},
};

小程序技术架构

/**
* 小程序双线程架构
*/

const miniProgramArchitecture = {
// 渲染层 (WebView)
renderLayer: {
responsibility: "页面渲染、用户交互",
technology: "WebView",
files: ["WXML", "WXSS"],
limitations: [
"无法直接操作DOM",
"无法使用浏览器API",
"受限的JavaScript能力",
],
},

// 逻辑层 (JavaScript Core)
logicLayer: {
responsibility: "业务逻辑、数据处理、API调用",
technology: "JavaScript Core / V8",
files: ["JavaScript"],
capabilities: ["网络请求", "本地存储", "设备API调用", "数据处理"],
},

// 通信机制
communication: {
method: "Native Bridge",
dataFlow: "逻辑层 ↔ Native ↔ 渲染层",
serialization: "JSON序列化",
performance: "异步通信,可能有延迟",
},
};

/**
* 小程序生命周期
*/
const miniProgramLifecycle = {
// 应用生命周期
app: {
onLaunch: "小程序初始化完成时触发,全局只触发一次",
onShow: "小程序启动,或从后台进入前台显示时触发",
onHide: "小程序从前台进入后台时触发",
onError: "小程序发生脚本错误或API调用失败时触发",
},

// 页面生命周期
page: {
onLoad: "页面加载时触发,一个页面只会调用一次",
onShow: "页面显示/切入前台时触发",
onReady: "页面初次渲染完成时触发,一个页面只会调用一次",
onHide: "页面隐藏/切入后台时触发",
onUnload: "页面卸载时触发",
},

// 组件生命周期
component: {
created: "组件实例刚刚被创建时执行",
attached: "组件实例进入页面节点树时执行",
ready: "组件在视图层布局完成后执行",
moved: "组件实例被移动到节点树另一个位置时执行",
detached: "组件实例被从页面节点树移除时执行",
},
};

微信小程序开发

项目结构和配置

/**
* 微信小程序项目结构
*/

const wechatMiniProgramStructure = {
// 项目根目录
root: {
"app.js": "小程序逻辑",
"app.json": "小程序公共配置",
"app.wxss": "小程序公共样式表",
"sitemap.json": "配置小程序及其页面是否允许被微信索引",
"project.config.json": "项目配置文件",
},

// 页面目录
pages: {
"pages/index/index.js": "页面逻辑",
"pages/index/index.wxml": "页面结构",
"pages/index/index.wxss": "页面样式表",
"pages/index/index.json": "页面配置",
},

// 组件目录
components: {
"components/custom/custom.js": "组件逻辑",
"components/custom/custom.wxml": "组件结构",
"components/custom/custom.wxss": "组件样式",
"components/custom/custom.json": "组件配置",
},

// 工具目录
utils: {
"utils/util.js": "工具函数",
"utils/request.js": "网络请求封装",
"utils/storage.js": "本地存储封装",
},
};

/**
* app.json 全局配置
*/
const appConfig = {
// 页面路径列表
pages: [
"pages/index/index",
"pages/profile/profile",
"pages/settings/settings",
],

// 全局的默认窗口表现
window: {
backgroundTextStyle: "light",
navigationBarBackgroundColor: "#fff",
navigationBarTitleText: "我的小程序",
navigationBarTextStyle: "black",
backgroundColor: "#f8f8f8",
enablePullDownRefresh: true,
onReachBottomDistance: 50,
},

// 底部 tab 栏的表现
tabBar: {
color: "#7A7E83",
selectedColor: "#3cc51f",
borderStyle: "black",
backgroundColor: "#ffffff",
list: [
{
pagePath: "pages/index/index",
iconPath: "images/icon_home.png",
selectedIconPath: "images/icon_home_selected.png",
text: "首页",
},
{
pagePath: "pages/profile/profile",
iconPath: "images/icon_profile.png",
selectedIconPath: "images/icon_profile_selected.png",
text: "我的",
},
],
},

// 网络超时时间
networkTimeout: {
request: 10000,
downloadFile: 10000,
},

// 是否开启 debug 模式
debug: false,

// 分包结构
subpackages: [
{
root: "packageA",
pages: ["pages/cat/cat", "pages/dog/dog"],
},
{
root: "packageB",
name: "pack2",
pages: ["pages/apple/apple", "pages/banana/banana"],
},
],

// 预下载分包
preloadRule: {
"pages/index/index": {
network: "all",
packages: ["packageA"],
},
},
};

页面开发

/**
* 微信小程序页面开发示例
*/

// pages/index/index.js
Page({
/**
* 页面的初始数据
*/
data: {
userInfo: null,
hasUserInfo: false,
canIUse: wx.canIUse("button.open-type.getUserInfo"),
motto: "Hello World",
logs: [],
items: [],
loading: false,
hasMore: true,
page: 1,
},

/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log("页面加载", options);
this.initPage();
},

/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
console.log("页面初次渲染完成");
},

/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
console.log("页面显示");
},

/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
console.log("页面隐藏");
},

/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
console.log("页面卸载");
},

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
console.log("下拉刷新");
this.refreshData();
},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
console.log("上拉触底");
this.loadMoreData();
},

/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
return {
title: "分享标题",
path: "/pages/index/index",
imageUrl: "/images/share.jpg",
};
},

/**
* 初始化页面
*/
initPage: function () {
this.getUserInfo();
this.loadData();
},

/**
* 获取用户信息
*/
getUserInfo: function () {
if (this.data.canIUse) {
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
wx.getUserInfo({
success: (res) => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true,
});
},
fail: (err) => {
console.error("获取用户信息失败", err);
},
});
} else {
// 在没有 open-type=getUserInfo 版本的兼容处理
wx.getUserInfo({
success: (res) => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true,
});
},
});
}
},

/**
* 加载数据
*/
loadData: function () {
this.setData({ loading: true });

wx.request({
url: "https://api.example.com/items",
method: "GET",
data: {
page: 1,
limit: 20,
},
success: (res) => {
if (res.statusCode === 200) {
this.setData({
items: res.data.items,
page: 1,
hasMore: res.data.hasMore,
});
}
},
fail: (err) => {
console.error("加载数据失败", err);
wx.showToast({
title: "加载失败",
icon: "none",
});
},
complete: () => {
this.setData({ loading: false });
wx.stopPullDownRefresh();
},
});
},

/**
* 刷新数据
*/
refreshData: function () {
this.setData({
page: 1,
items: [],
hasMore: true,
});
this.loadData();
},

/**
* 加载更多数据
*/
loadMoreData: function () {
if (!this.data.hasMore || this.data.loading) {
return;
}

const nextPage = this.data.page + 1;
this.setData({ loading: true });

wx.request({
url: "https://api.example.com/items",
method: "GET",
data: {
page: nextPage,
limit: 20,
},
success: (res) => {
if (res.statusCode === 200) {
this.setData({
items: [...this.data.items, ...res.data.items],
page: nextPage,
hasMore: res.data.hasMore,
});
}
},
fail: (err) => {
console.error("加载更多数据失败", err);
},
complete: () => {
this.setData({ loading: false });
},
});
},

/**
* 处理用户点击事件
*/
onItemTap: function (e) {
const item = e.currentTarget.dataset.item;
wx.navigateTo({
url: `/pages/detail/detail?id=${item.id}`,
});
},

/**
* 处理按钮点击
*/
onButtonTap: function () {
wx.showModal({
title: "提示",
content: "确定要执行此操作吗?",
success: (res) => {
if (res.confirm) {
console.log("用户点击确定");
this.performAction();
} else if (res.cancel) {
console.log("用户点击取消");
}
},
});
},

/**
* 执行操作
*/
performAction: function () {
wx.showLoading({
title: "处理中...",
});

// 模拟异步操作
setTimeout(() => {
wx.hideLoading();
wx.showToast({
title: "操作成功",
icon: "success",
});
}, 2000);
},
});

WXML 模板语法

<!-- pages/index/index.wxml -->
<view class="container">
<!-- 用户信息展示 -->
<view class="userinfo" wx:if="{{hasUserInfo}}">
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</view>

<!-- 登录按钮 -->
<button wx:else open-type="getUserInfo" bindgetuserinfo="getUserInfo" class="login-btn">
获取头像昵称
</button>

<!-- 数据列表 -->
<view class="list-container">
<view class="list-header">
<text class="list-title">数据列表</text>
<button bindtap="refreshData" class="refresh-btn">刷新</button>
</view>

<!-- 列表项 -->
<view class="list">
<view
wx:for="{{items}}"
wx:key="id"
wx:for-item="item"
wx:for-index="index"
class="list-item"
data-item="{{item}}"
bindtap="onItemTap"
>
<image class="item-image" src="{{item.imageUrl}}" mode="aspectFill"></image>
<view class="item-content">
<text class="item-title">{{item.title}}</text>
<text class="item-desc">{{item.description}}</text>
<view class="item-meta">
<text class="item-time">{{item.createTime}}</text>
<text class="item-status" wx:if="{{item.status === 'active'}}">活跃</text>
</view>
</view>
</view>
</view>

<!-- 加载状态 -->
<view class="loading" wx:if="{{loading}}">
<text>加载中...</text>
</view>

<!-- 没有更多数据 -->
<view class="no-more" wx:if="{{!hasMore && items.length > 0}}">
<text>没有更多数据了</text>
</view>

<!-- 空状态 -->
<view class="empty" wx:if="{{items.length === 0 && !loading}}">
<text>暂无数据</text>
</view>
</view>

<!-- 操作按钮 -->
<view class="actions">
<button bindtap="onButtonTap" class="action-btn">执行操作</button>
</view>
</view>

WXSS 样式

/* pages/index/index.wxss */

.container {
padding: 20rpx;
background-color: #f8f8f8;
min-height: 100vh;
}

/* 用户信息样式 */
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
background-color: #fff;
border-radius: 20rpx;
margin-bottom: 20rpx;
}

.userinfo-avatar {
width: 128rpx;
height: 128rpx;
border-radius: 50%;
margin-bottom: 20rpx;
}

.userinfo-nickname {
font-size: 32rpx;
color: #333;
font-weight: bold;
}

.login-btn {
width: 300rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #07c160;
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
margin: 40rpx auto;
}

/* 列表样式 */
.list-container {
background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
}

.list-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #eee;
}

.list-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}

.refresh-btn {
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
background-color: #f0f0f0;
color: #666;
border-radius: 30rpx;
font-size: 24rpx;
}

.list-item {
display: flex;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}

.list-item:last-child {
border-bottom: none;
}

.item-image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
margin-right: 20rpx;
flex-shrink: 0;
}

.item-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}

.item-title {
font-size: 30rpx;
color: #333;
font-weight: bold;
margin-bottom: 10rpx;
}

.item-desc {
font-size: 26rpx;
color: #666;
line-height: 1.4;
margin-bottom: 10rpx;
}

.item-meta {
display: flex;
justify-content: space-between;
align-items: center;
}

.item-time {
font-size: 24rpx;
color: #999;
}

.item-status {
font-size: 22rpx;
color: #07c160;
background-color: #f0f9ff;
padding: 4rpx 12rpx;
border-radius: 12rpx;
}

/* 状态样式 */
.loading,
.no-more,
.empty {
text-align: center;
padding: 40rpx;
color: #999;
font-size: 26rpx;
}

/* 操作按钮 */
.actions {
margin-top: 40rpx;
text-align: center;
}

.action-btn {
width: 300rpx;
height: 80rpx;
line-height: 80rpx;
background-color: #1989fa;
color: #fff;
border-radius: 40rpx;
font-size: 28rpx;
}

小程序 API 和能力

常用 API 分类

/**
* 小程序API分类
*/

const miniProgramAPIs = {
// 界面API
ui: {
navigation: ["wx.navigateTo", "wx.redirectTo", "wx.switchTab"],
interaction: ["wx.showToast", "wx.showModal", "wx.showActionSheet"],
loading: ["wx.showLoading", "wx.hideLoading"],
pullRefresh: ["wx.startPullDownRefresh", "wx.stopPullDownRefresh"],
},

// 网络API
network: {
request: ["wx.request", "wx.uploadFile", "wx.downloadFile"],
websocket: ["wx.connectSocket", "wx.sendSocketMessage"],
},

// 数据存储
storage: {
sync: ["wx.setStorageSync", "wx.getStorageSync", "wx.removeStorageSync"],
async: ["wx.setStorage", "wx.getStorage", "wx.removeStorage"],
},

// 设备API
device: {
system: ["wx.getSystemInfo", "wx.getSystemInfoSync"],
network: ["wx.getNetworkType", "wx.onNetworkStatusChange"],
battery: ["wx.getBatteryInfo", "wx.getBatteryInfoSync"],
vibrate: ["wx.vibrateLong", "wx.vibrateShort"],
},

// 媒体API
media: {
image: ["wx.chooseImage", "wx.previewImage", "wx.saveImageToPhotosAlbum"],
video: ["wx.chooseVideo", "wx.saveVideoToPhotosAlbum"],
audio: ["wx.playVoice", "wx.stopVoice"],
camera: ["wx.createCameraContext"],
},

// 位置API
location: {
get: ["wx.getLocation", "wx.chooseLocation"],
map: ["wx.createMapContext", "wx.openLocation"],
},

// 文件API
file: {
manager: ["wx.getFileSystemManager"],
temp: ["wx.saveFile", "wx.getSavedFileList"],
},

// 开放接口
open: {
login: ["wx.login", "wx.checkSession"],
userInfo: ["wx.getUserInfo", "wx.getUserProfile"],
payment: ["wx.requestPayment"],
share: ["wx.updateShareMenu", "wx.hideShareMenu"],
},
};

/**
* API使用示例
*/

// 网络请求封装
class RequestManager {
constructor() {
this.baseURL = "https://api.example.com";
this.timeout = 10000;
}

/**
* 发起请求
*/
request(options) {
return new Promise((resolve, reject) => {
wx.request({
url: this.baseURL + options.url,
method: options.method || "GET",
data: options.data || {},
header: {
"Content-Type": "application/json",
...options.header,
},
timeout: this.timeout,
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data);
} else {
reject(new Error(`请求失败: ${res.statusCode}`));
}
},
fail: (err) => {
reject(err);
},
});
});
}

/**
* GET请求
*/
get(url, data, options = {}) {
return this.request({
url,
method: "GET",
data,
...options,
});
}

/**
* POST请求
*/
post(url, data, options = {}) {
return this.request({
url,
method: "POST",
data,
...options,
});
}

/**
* 上传文件
*/
upload(url, filePath, formData = {}) {
return new Promise((resolve, reject) => {
wx.uploadFile({
url: this.baseURL + url,
filePath,
name: "file",
formData,
success: (res) => {
try {
const data = JSON.parse(res.data);
resolve(data);
} catch (error) {
reject(new Error("响应数据解析失败"));
}
},
fail: reject,
});
});
}
}

// 存储管理
class StorageManager {
/**
* 设置存储
*/
static set(key, value, sync = true) {
try {
const data = JSON.stringify(value);
if (sync) {
wx.setStorageSync(key, data);
} else {
return new Promise((resolve, reject) => {
wx.setStorage({
key,
data,
success: resolve,
fail: reject,
});
});
}
} catch (error) {
console.error("存储数据失败:", error);
throw error;
}
}

/**
* 获取存储
*/
static get(key, defaultValue = null, sync = true) {
try {
if (sync) {
const data = wx.getStorageSync(key);
return data ? JSON.parse(data) : defaultValue;
} else {
return new Promise((resolve, reject) => {
wx.getStorage({
key,
success: (res) => {
try {
const data = JSON.parse(res.data);
resolve(data);
} catch (error) {
resolve(defaultValue);
}
},
fail: () => resolve(defaultValue),
});
});
}
} catch (error) {
console.error("获取存储数据失败:", error);
return defaultValue;
}
}
}

总结

跨平台小程序开发已经成为现代移动应用开发的重要趋势。通过合理选择技术方案、遵循最佳实践,可以大大提高开发效率,降低维护成本。

无论选择哪种技术栈,都需要深入理解各平台的特性和限制,在统一性和平台特色之间找到最佳平衡点。

官方文档链接

小程序平台官方资源

微信小程序

支付宝小程序

百度小程序

字节跳动小程序

QQ 小程序

跨平台开发框架

相关技术资源