一、什么是微信小程序
微信小程序是一种运行在微信内的轻量级应用,无需下载安装,即点即用。它使用微信提供的原生开发方式,通过 WXML、WXSS、JavaScript/TypeScript 和 JSON 四种文件完成开发。
小程序 vs H5 vs App
| 维度 | 微信小程序 | H5(移动端网页) | 原生 App |
|---|
| 运行环境 | 微信客户端(原生渲染) | 浏览器(WebView) | 系统原生 |
| 安装 | 无需安装,扫码/搜索即用 | 无需安装 | 需从应用商店下载 |
| 发布更新 | 微信审核后发布 | 即时发布 | 应用商店审核 |
| 性能 | 接近原生 | 依赖浏览器性能 | 最好 |
| 离线能力 | 部分支持 | 有限 | 完整 |
| 系统 API 访问 | 微信 SDK 封装 | 浏览器 API | 系统 API 完整 |
| 微信生态 | 可直接调用微信支付、登录、分享 | 需 H5 中转 | 需接入微信 SDK |
二、项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| my-miniprogram/ ├── app.json # 全局配置 ├── app.wxss # 全局样式 ├── app.ts / app.js # 全局逻辑(App 实例) ├── project.config.json # 项目配置(IDE 用) ├── sitemap.json # 搜索配置 ├── pages/ # 页面 │ ├── index/ │ │ ├── index.wxml # 模板 │ │ ├── index.wxss # 页面样式 │ │ ├── index.ts # 页面逻辑 │ │ └── index.json # 页面配置 │ └── logs/ │ └── ... ├── components/ # 自定义组件 │ └── card/ │ ├── card.wxml │ ├── card.wxss │ ├── card.ts │ └── card.json ├── utils/ # 工具函数 │ └── api.ts ├── images/ # 图片资源 └── style/ # 公共样式
|
app.json——全局配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| { "pages": [ "pages/index/index", "pages/logs/logs" ], "window": { "navigationBarTitleText": "我的小程序", "navigationBarBackgroundColor": "#409eff", "backgroundColor": "#f5f5f5" }, "tabBar": { "color": "#999", "selectedColor": "#409eff", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "images/home.png" }, { "pagePath": "pages/logs/logs", "text": "日志", "iconPath": "images/log.png" } ] }, "usingComponents": {}, "permission": { "scope.userLocation": { "desc": "您的位置信息将用于展示附近门店" } }, "requiredPrivateInfos": ["getLocation"], "networkTimeout": { "request": 10000, "connectSocket": 10000 } }
|
关键字段:
| 字段 | 说明 |
|---|
pages | 页面路径列表,第一项为首页 |
window | 全局窗口样式 |
tabBar | 底部 Tab 栏配置 |
usingComponents | 全局注册自定义组件 |
permission | 权限申请说明 |
requiredPrivateInfos | 需用户授权才能使用的 API |
networkTimeout | 网络超时配置 |
三、页面生命周期
App 生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| App({ onLaunch() { wx.getSystemInfo({ success: res => console.log(res) }); }, onShow() { }, onHide() { }, globalData: { userInfo: null, }, });
|
Page 生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| Page({ data: { message: 'Hello', userList: [], },
onLoad(query) { console.log('页面参数:', query.id); },
onShow() { this.getUserList(); },
onReady() { },
onHide() { },
onUnload() { },
onPullDownRefresh() { this.getUserList(); wx.stopPullDownRefresh(); },
onReachBottom() { this.loadMore(); },
onShareAppMessage() { return { title: '分享标题', path: '/pages/index/index' }; },
async getUserList() { const res = await wx.request({ url: '/api/users' }); this.setData({ userList: res.data }); }, });
|
| 生命周期 | 触发时机 | 常见用途 |
|---|
onLoad | 页面加载时 | 获取页面参数、初始化数据 |
onShow | 页面显示时 | 刷新数据(每次进入都执行) |
onReady | 页面渲染完成 | 获取节点信息、动画初始化 |
onHide | 页面隐藏时 | 暂停定时器、暂停播放 |
onUnload | 页面卸载时 | 清理资源 |
onPullDownRefresh | 下拉刷新 | 重新拉取数据 |
onReachBottom | 滚动触底 | 分页加载 |
四、数据绑定与渲染
WXML 模板语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <view>{{ message }}</view> <view id="item-{{id}}"></view>
<view wx:if="{{isLogin}}">已登录</view> <view wx:else>未登录</view>
<view wx:if="{{score >= 90}}">A</view> <view wx:elif="{{score >= 60}}">B</view> <view wx:else>C</view>
<view wx:for="{{userList}}" wx:for-item="user" wx:for-index="i" wx:key="id"> <text>{{i + 1}}.{{user.name}}</text> </view>
<import src="template.wxml" /> <template is="card" data="{{item}}" />
|
wx:if vs hidden
| wx:if | hidden |
|---|
| 渲染方式 | 条件为真时才渲染 DOM | 始终渲染,只是控制 display |
| 切换代价 | 高(节点创建/销毁) | 低(仅隐藏) |
| 适用场景 | 切换频率低 | 切换频率高 |
setData——更新页面数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| Page({ data: { count: 0, user: { name: 'Alice', age: 25 }, list: [], },
increment() { this.setData({ count: this.data.count + 1, }); },
updateUser() { this.setData({ 'user.name': 'Bob', }); },
addItem(item) { this.setData({ list: [...this.data.list, item], }); }, });
|
setData 的性能注意事项:
1 2 3 4 5
| ├── setData 是 Native 与 JS 线程的通信,有成本 ├── 每次 setData 不要传递过大体积数据(建议 < 1MB) ├── 不在 setData 中传递不需要渲染的数据 ├── 频繁调用的场景(如进度条)可考虑节流 └── 用路径更新代替整体更新:'user.name' 优于 user: { ... }
|
五、WXSS 样式
尺寸单位:rpx
1 2 3 4 5 6 7 8 9 10 11 12
|
.container { width: 750rpx; padding: 20rpx; font-size: 28rpx; }
.card { width: 340rpx; margin: 10rpx; }
|
WXSS 支持大部分 CSS 属性,但有差异:
1 2 3
| 支持:flex、grid、position、transition、animation、transform 不支持:部分 CSS3 高级选择器、backdrop-filter(部分版本支持) 无需考虑:浏览器兼容性(微信内置浏览器统一)
|
全局 vs 页面样式
1 2 3 4 5 6 7 8 9 10 11
| page { background-color: #f5f5f5; font-size: 28rpx; color: #333; }
.index-container { padding: 20rpx; }
|
内联样式
1
| <view style="color: {{dynamicColor}}; font-size: {{size}}px"></view>
|
注意:小程序支持用变量动态设置内联样式,但 style 中的属性名用驼峰(backgroundColor)或连字符(background-color)均可。
六、路由导航
1 2 3 4 5 6 7 8 9 10 11
| <navigator url="/pages/detail/detail?id=1">跳转详情</navigator> <navigator url="/pages/index/index" open-type="switchTab">跳转到 Tab</navigator> <navigator url="/pages/login/login" open-type="redirect">重定向</navigator>
wx.navigateTo({ url: '/pages/detail/detail?id=1' }); wx.redirectTo({ url: '/pages/login/login' }); wx.switchTab({ url: '/pages/index/index' }); wx.navigateBack({ delta: 1 }); wx.reLaunch({ url: '/pages/index/index' });
|
| API | 当前页面 | 跳转目标 | 使用场景 |
|---|
navigateTo | 保留 | 非 Tab 页 | 详情页、表单页 |
redirectTo | 关闭 | 非 Tab 页 | 登录页、支付结果 |
switchTab | 关闭 | Tab 页 | 切换底部 Tab |
navigateBack | 关闭 | 上一页 | 返回 |
reLaunch | 全部关闭 | 任意页 | 异常后重置 |
七、自定义组件
1 2 3 4 5
| { "component": true, "usingComponents": {} }
|
1 2 3 4 5 6 7 8 9
| <view class="card" bind:tap="onClick"> <image src="{{avatar}}" class="avatar" mode="aspectFill" /> <view class="info"> <text class="name">{{name}}</text> <text class="desc">{{desc}}</text> </view> <slot /> </view>
|
1 2 3 4 5 6 7
| .card { display: flex; padding: 20rpx; background: #fff; border-radius: 12rpx; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| Component({ properties: { name: { type: String, value: '' }, desc: { type: String, value: '' }, avatar: { type: String, value: '' }, },
data: { },
lifetimes: { attached() { }, detached() { }, },
pageLifetimes: { show() { }, hide() { }, },
methods: { onClick() { this.triggerEvent('tap', { id: this.properties.name }); }, }, });
|
组件通信
1 2 3 4
| 父 → 子:通过 properties 传递数据 子 → 父:通过 triggerEvent 触发事件 兄弟组件:通过父组件中转或全局事件 跨页面:全局数据 App.globalData、缓存、EventBus
|
1 2 3 4 5 6 7
| <card name="{{user.name}}" desc="{{user.desc}}" avatar="{{user.avatar}}" bind:tap="onCardTap" />
|
1 2 3
| onCardTap(e: WechatMiniprogram.CustomEvent) { console.log('点击了:', e.detail.id); }
|
八、常用 API
网络请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const request = <T>(url: string, data?: Record<string, any>, method = 'GET'): Promise<T> => { return new Promise((resolve, reject) => { wx.request({ url: `https://api.example.com${url}`, data, method, header: { Authorization: `Bearer ${wx.getStorageSync('token')}`, }, success: res => resolve(res.data as T), fail: reject, }); }); };
async function getUserList() { const res = await request<{ name: string }[]>('/users'); this.setData({ userList: res }); }
|
本地存储
1 2 3 4 5 6 7 8
| wx.setStorageSync('key', 'value'); const val = wx.getStorageSync('key'); wx.removeStorageSync('key'); wx.clearStorageSync();
wx.setStorage({ key: 'token', data: 'xxx' });
|
用户登录
1 2 3 4 5 6 7 8 9 10 11 12 13
| App({ async onLaunch() { const { code } = await wx.login();
const res = await request('/auth/login', { code });
wx.setStorageSync('token', res.token); }, });
|
获取用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <button open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">获取头像</button>
onChooseAvatar(e) { this.setData({ avatarUrl: e.detail.avatarUrl }); }
<button open-type="getPhoneNumber" bind:getphonenumber="onGetPhone">获取手机号</button>
async onGetPhone(e) { if (e.detail.code) { const res = await request('/auth/phone', { code: e.detail.code }); this.setData({ phone: res.phoneNumber }); } }
|
支付
1 2 3 4 5 6 7 8 9 10 11 12 13
| async function wxPay(orderId: string) { const { payment } = await request('/pay/unified-order', { orderId });
wx.requestPayment({ timeStamp: payment.timeStamp, nonceStr: payment.nonceStr, package: payment.package, signType: 'RSA', paySign: payment.paySign, success() { wx.showToast({ title: '支付成功' }); }, fail() { wx.showToast({ title: '支付失败', icon: 'error' }); }, }); }
|
九、小程序与 H5 的差异
不支持 DOM / BOM API
小程序环境中没有 window、document、navigator 等浏览器全局对象。常见替代:
| 浏览器 API | 小程序替代 |
|---|
document.querySelector | wx.createSelectorQuery |
window.addEventListener | wx.onAppShow、wx.onAppHide |
localStorage | wx.setStorageSync |
fetch / XMLHttpRequest | wx.request |
Image 对象 | wx.getImageInfo |
audio / video 标签 | wx.createInnerAudioContext |
navigator.geolocation | wx.getLocation |
window.innerWidth | wx.getSystemInfoSync().windowWidth |
包大小限制
1 2 3
| 主包:不超过 2MB(含代码 + 资源) 分包:每个子包不超过 2MB,总包不超过 20MB 静态资源(图片/音频等)建议放 CDN,不要打包进项目
|
分包配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| { "pages": ["pages/index/index"], "subPackages": [ { "root": "packageA", "pages": ["pages/order/order", "pages/cart/cart"] }, { "root": "packageB", "independent": true, "pages": ["pages/activity/activity"] } ], "preloadRule": { "pages/index/index": { "network": "all", "packages": ["packageA"] } } }
|
渲染性能
1 2 3 4 5
| ├── 双线程模型:渲染层(WebView)+ 逻辑层(JSCore) ├── setData 跨线程通信有开销 ├── 频繁或大数据量 setData 会导致卡顿 ├── 列表渲染建议使用 wx:key + 分页加载 └── 复杂页面考虑用 <scroll-view> 替代 Page 原生滚动(精细控制)
|
十、常见问题
Q1: 小程序页面跳转后 data 还在吗
页面跳转后,当前页面的 data 保存在内存中(navigateTo 保留,redirectTo 销毁)。返回后 data 恢复,onLoad 不会重新触发。如果每次进入页面都需要刷新数据,把请求放在 onShow 中。
Q2: 如何全局管理登录态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| App({ globalData: { token: '' },
async checkLogin() { const token = wx.getStorageSync('token'); if (!token) return false;
try { const res = await wx.request({ url: '/api/auth/verify' }); return res.statusCode === 200; } catch { return false; } }, });
const app = getApp(); Page({ async onShow() { const isLogin = await app.checkLogin(); if (!isLogin) { wx.navigateTo({ url: '/pages/login/login' }); } }, });
|
Q3: 小程序如何处理大图片
1 2 3 4 5
| ├── 图片用 CDN 链接,不放入项目包 ├── 列表中的图片用 image 组件的 lazy-load 属性 ├── mode="widthFix" 保持比例自适应 ├── 预加载首屏之外的图片 └── 使用 webp 格式(CDN 转换)
|
Q4: 小程序和 H5 哪个更适合做商城
| 场景 | 推荐 | 原因 |
|---|
| 强依赖微信生态(支付、分享) | 小程序 | 原生支付体验好,可分享到聊天/朋友圈 |
| 需要频繁更新、无需审核 | H5 | 审核周期 1-7 天 |
| 高交互复杂功能 | 小程序 | 性能接近原生 |
| SEO 需求 | H5 | 小程序内容不可被搜索引擎索引 |
| 超 2MB 业务复杂 | 小程序(分包) | 分包后可超过 2MB |
十一、推荐学习路径
- 注册小程序账号,下载开发者工具,跑通官方 demo
- 掌握页面生命周期和
setData 更新机制 - 写一个列表页 + 详情页,理解导航和数据传递
- 封装自定义组件,理解组件通信
- 接入微信登录和支付流程
- 配置分包和预加载,优化首屏性能和包体积
- 阅读小程序官方文档,逐个了解常用 API