微信小程序
配置文件
全局配置文件 app.json
json
{
"entryPagePath": "pages/index/index",
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"navigationBarTitleText": "Demo",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f8f8f8"
},
"tabBar": {
"selectedColor": "#d0021b",
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "/assets/images/home.png",
"selectedIconPath": "/assets/images/home_active.png"
}]
}
}
- entryPagePath:小程序默认启动首页
- pages:页面路径列表,第一个页面为首页
- window:全局窗口表现
- navigationBarBackgroundColor:导航栏背景色
- navigationBarTextStyle导航栏标题颜色
- navigationBarTitleText:导航栏标题
- tabBar:底部/顶部tab栏配置
- selectedColor:选中颜色
- list:tab列表,至少2个最多5个
页面配置文件 page.json
json
{
"usingComponents": {}
}
- 只覆盖app.json中window的配置
- 无需写window键名
- 页面中.json文件非必须
工程配置文件 project.config.json
json
{
"appid": "wx99a5813d6e1698c4",
"compileType": "miniprogram",
"libVersion": "3.7.3",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
}
}
- 保存项目级别的配置,提交到代码仓库
- 用于团队协作时统一开发环境配置
- 包含小程序开发工具、编译、上传等配置
TIP
json
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "miniprogram-1",
"setting": {
"compileHotReLoad": true
}
}
- 保存开发者个人的本地配置,不应提交到代码仓库
- 用于存储个人开发偏好和敏感信息
- 优先级高于 project.config.json
索引配置文件 sitemap.json
json
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}
- 控制小程序页面是否被微信索引
- 影响小程序在微信内的搜索展现
渲染模式
WebView 渲染模式
- 渲染引擎:基于浏览器 WebView 内核(类似 Chrome/WebKit)
- 特点:
- 兼容性好:支持所有基础库版本,适合老项目或需要广泛兼容的场景
- 性能一般:渲染管线较长,JS 逻辑可能阻塞 UI 渲染,动画和交互可能卡顿
- 开发体验:类似传统 H5 开发,支持完整 CSS/JS,但受限于 WebView 能力
Skyline 渲染模式
- 渲染引擎:微信自研的高性能引擎(底层可能基于 Flutter 优化)
- 特点:
- 性能更强:
- 首屏加载快 17.6%,渲染耗时减少 50%
- 支持 Worklet 动画(UI 线程直接执行,无通信延迟)
- 更丰富的特性:
- 支持 Flex/Grid 布局、手势系统、共享元素动画等原生级交互
- WXSS 预编译(比运行时解析快 5 倍)
- 兼容性限制:
- 需基础库 ≥ 2.29.2(推荐 ≥ 3.0.0)
- 部分 CSS 特性被精简,需适配
- 性能更强:
事件系统
事件类型
- touchstart:手指触摸动作开始
- touchmove:手指触摸后移动
- touchcancel:手指触摸动作被打断,如来电提醒,弹窗
- touchend:手指触摸动作结束
- tap:手指触摸后马上离开
- longpress:手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发
事件绑定
html
<!-- 普通绑定(不阻止事件冒泡) -->
<view bindtap="handleTap">点击我</view>
<!-- 阻止冒泡绑定 -->
<view catchtap="handleTap">点击我(阻止冒泡)</view>
<!-- 互斥事件绑定 -->
<view mut-bind:tap="handleMutTap1">互斥1</view>
<view mut-bind:tap="handleMutTap2">互斥2</view>
<!-- 捕获阶段事件绑定 -->
<view capture-bind:touchstart="handleCaptureStart">捕获阶段</view>
<!-- 自定义组件事件绑定 -->
<custom-component bind:myevent="handleCustomEvent" />
事件对象
js
handleTap(e) {
console.log(e.type); // 事件类型
console.log(e.timeStamp); // 事件触发时间戳
console.log(e.target); // 触发事件的源组件
console.log(e.currentTarget); // 当前绑定事件的组件
console.log(e.detail); // 额外信息(如点击位置)
console.log(e.touches); // 触摸点信息
console.log(e.changedTouches); // 变化触摸点信息
}
TIP
特性 | target | currentTarget |
---|---|---|
指向对象 | 实际触发事件的组件 | 绑定事件处理函数的组件 |
冒泡影响 | 不随冒泡改变 | 随冒泡层级变化 |
dataset获取 | 获取触发点的data属性 | 获取绑定元素的data属性 |
组件层级 | 可能是深层子组件 | 始终是绑定事件的组件 |
实际用途 | 确定事件触发源头 | 确定事件处理上下文 |
- 当事件绑定在触发事件的元素本身时,两者指向相同
html
<!-- 点击按钮时,target和currentTarget都指向这个button -->
<button
bindtap="handleButtonTap"
data-id="123"
>点击我</button>
js
handleButtonTap(e) {
// e.target === e.currentTarget
console.log(e.target.dataset.id); // "123"
console.log(e.currentTarget.dataset.id); // "123"
}
- 当事件通过冒泡传递到父元素时,两者指向不同
html
<view bindtap="handleParentTap" class="parent">
<view class="child">
<text class="grandchild">点击这里</text>
</view>
</view>
js
handleParentTap(e) {
console.log('target:', e.target); // 指向实际点击的text元素
console.log('currentTarget:', e.currentTarget); // 指向绑定事件的view元素
}
事件传参
- dataset:收集所有以data-开头的自定义属性
html
<view data-user-id="123" data-role="admin" bindtap="handleTap">
<text data-note="test">点击区域</text>
</view>
js
handleTap(e) {
// 点击text时:
console.log(e.target.dataset); // {note: "test"}
console.log(e.currentTarget.dataset); // {userId: "123", role: "admin"}
}
- mark:实现跨层级数据传递(基础库2.7.1+)
html
<view bindtap="handleTap" mark:user="{{{id: 1, name: 'Alice'}}}">
点击获取mark数据
</view>
js
handleTap(e) {
console.log(e.mark.user); // {id: 1, name: "Alice"}
}
路由导航
声明式导航
- 基础使用
html
<!-- 普通跳转 -->
<navigator url="/pages/detail/detail?id=123">查看详情</navigator>
<!-- 新窗口打开 -->
<navigator url="/pages/webview/webview" open-type="navigate">
打开网页
</navigator>
- open-type 类型
属性值 | 对应API | 说明 |
---|---|---|
navigate | wx.navigateTo | 默认值,保留当前页 |
redirect | wx.redirectTo | 关闭当前页跳转 |
switchTab | wx.switchTab | 切换Tab页 |
reLaunch | wx.reLaunch | 关闭所有页跳转 |
navigateBack | wx.navigateBack | 返回上一页 |
编程式导航
- wx.navigateTo
- 保留当前页面(可返回)
- 可跳转到非TabBar页面
- 最多保留10层页面栈
js
wx.navigateTo({
url: '/pages/detail/detail?id=123&name=product',
events: {
// 接收被打开页面发送的数据
acceptData(data) {
console.log('接收数据:', data)
}
},
success(res) {
console.log('跳转成功', res.eventChannel)
}
})
- wx.redirectTo
- 关闭当前页面(不可返回)
- 不增加页面栈深度
- 适用于登录后跳转等场景
js
wx.redirectTo({
url: '/pages/home/home?from=login'
})
- wx.switchTab
- 关闭所有非TabBar页面
- 只能跳转到tabBar配置的页面
- 不支持传递参数
js
wx.switchTab({
url: '/pages/index/index'
})
- wx.reLaunch
- 关闭所有页面(清空页面栈)
- 打开新页面(可跳转到任意页面)
- 适合全局状态重置场景
js
wx.reLaunch({
url: '/pages/main/main?reset=true'
})
- wx.navigateBack
- 返回上一页面或多层页面
- 可传递返回数据
js
// 返回上一页
wx.navigateBack()
// 返回指定层数
wx.navigateBack({
delta: 2 // 返回上2层
})
// 返回时传递数据
const pages = getCurrentPages()
const prevPage = pages[pages.length - 2]
prevPage.setData({ backData: '返回数据' })
wx.navigateBack()
setData 方法
更新单个数据
js
Page({
data: {
message: 'Hello'
},
updateMessage() {
this.setData({
message: 'Hello World!'
})
}
})
更新多个数据
js
this.setData({
userName: '张三',
userAge: 28,
isVIP: true
})
更新嵌套数据
js
Page({
data: {
user: {
name: '李四',
address: {
city: '北京',
district: '朝阳区'
}
}
},
updateUser() {
this.setData({
'user.name': '王五',
'user.address.district': '海淀区'
})
}
})
数据路径更新
js
// 更新数组特定项
this.setData({
'list[2].name': '新名称'
})
// 使用变量作为路径
const index = 3
const field = 'status'
this.setData({
[`list[${index}].${field}`]: 'completed'
})
使用回调函数
js
this.setData({
counter: this.data.counter + 1
}, () => {
// 数据更新完成后的回调
console.log('数据已更新,当前值:', this.data.counter)
wx.showToast({ title: '更新成功' })
})
批量更新优化
js
// 不推荐:多次调用setData
this.setData({ a: 1 })
this.setData({ b: 2 })
this.setData({ c: 3 })
// 推荐:单次批量更新
this.setData({
a: 1,
b: 2,
c: 3
})
TIP
调试技巧
js
// 封装调试setData
function debugSetData(data) {
console.log('setData调用:', data)
const start = performance.now()
this.setData(data, () => {
console.log(`更新完成,耗时: ${(performance.now() - start).toFixed(2)}ms`)
})
}
wx.request 方法
HTTP 方法
方法 | 用途 | Header 使用场景 | Data 使用场景 | 示例 |
---|---|---|---|---|
GET | 获取数据 | 通常无需特殊 Header 可选:Cache-Control | URL 参数data: {page:1, size:10} → /api?page=1&size=10 | wx.request({ url:'/api', data:{q:'搜索词'} }) |
POST | 创建数据 | 必设:Content-Typeapplication/json application/x-www-form-urlencoded | 请求体内容data: {name:'张三'} | wx.request({ method:'POST', header:{'Content-Type':'application/json'}, data:{...} }) |
PUT | 更新数据 | 同 POST | 请求体内容 提供完整资源数据 data: {id:123, title:'新标题'} | wx.request({ method:'PUT', url:'/api/123', data:{...} }) |
DELETE | 删除数据 | 通常只需认证 Header | 通常为空 偶尔用于批量删除 | wx.request({ method:'DELETE', url:'/api/123' }) |
Header 使用指南
Header 类型 | 常用值 | 使用场景 | 是否必须 | 示例 |
---|---|---|---|---|
Content-Type | application/json | POST/PUT 发送 JSON 数据 | ✅ 是 | 'Content-Type': 'application/json' |
application/x-www-form-urlencoded | 表单提交 | ⚠️ 按需 | 'Content-Type': 'application/x-www-form-urlencoded' | |
multipart/form-data | 文件上传 | ❌ 否 | (小程序自动设置) | |
Authorization | Bearer <token> | 身份认证 | ⚠️ 按需 | 'Authorization': 'Bearer xyz123' |
Cache-Control | no-cache | 禁用缓存 | ⚠️ 按需 | 'Cache-Control': 'no-cache' |
max-age=3600 | 缓存 1 小时 | ⚠️ 按需 | 'Cache-Control': 'max-age=3600' | |
X-CSRF-Token | 任意字符串 | 防跨站攻击 | ⚠️ 按需 | 'X-CSRF-Token': 'abc456' |
Data 使用指南
方法 | Data 格式 | 示例 | 注意事项 |
---|---|---|---|
GET | 键值对对象 | data: {page:1, size:10} | 自动转为 URL 参数:/path?page=1&size=10 |
POST | JSON 对象 | data: {name:'李四', age:25} | 需配合 Content-Type: application/json |
表单字符串 | data: 'name=李四&age=25' | 需配合 Content-Type: application/x-www-form-urlencoded | |
PUT | JSON 对象 | data: {id:123, title:'新标题'} | 应提供完整资源数据(全量更新) |
DELETE | 通常为空 | data: null | 资源 ID 放 URL 中:/resource/123 |
本地存储
API 方法 | 类型 | 功能描述 | 参数说明 | 返回值/回调 | 使用场景 |
---|---|---|---|---|---|
wx.setStorage | 异步 | 存储数据到本地 | key : 键名data : 值(任意类型) | success/fail/complete 回调 | 非关键操作,无需立即结果 |
wx.setStorageSync | 同步 | 同步存储数据 | key : 键名data : 值 | 无 | 需立即确认存储结果的操作 |
wx.getStorage | 异步 | 读取本地存储数据 | key : 键名 | success 返回数据 | 非阻塞读取 |
wx.getStorageSync | 同步 | 同步读取数据 | key : 键名 | 对应 key 的数据 | 需要立即使用数据的场景 |
wx.removeStorage | 异步 | 删除指定 key 的存储数据 | key : 键名 | success/fail/complete 回调 | 清理特定数据 |
wx.removeStorageSync | 同步 | 同步删除数据 | key : 键名 | 无 | 需要立即删除确认的操作 |
wx.clearStorage | 异步 | 清空所有本地数据 | 无参数 | success/fail/complete 回调 | 用户退出登录时清理数据 |
wx.clearStorageSync | 同步 | 同步清空所有数据 | 无参数 | 无 | 需要立即清空存储的场景 |
存储数据
js
// 异步存储 - 适合非关键操作
wx.setStorage({
key: 'userInfo',
data: {name: '张三', id: 'U123456'},
success: () => console.log('存储成功'),
fail: err => console.error('存储失败', err)
})
// 同步存储 - 需立即确认的场景
try {
wx.setStorageSync('token', 'Bearer xyz789')
console.log('Token 已存储')
} catch (e) {
console.error('存储失败', e)
}
获取数据
js
// 异步读取
wx.getStorage({
key: 'userInfo',
success: res => console.log('用户数据:', res.data),
fail: () => console.log('数据不存在')
})
// 同步读取 - 适合需要立即使用的数据
try {
const token = wx.getStorageSync('token')
if (token) {
console.log('获取到Token:', token)
} else {
console.log('Token不存在')
}
} catch (e) {
console.error('读取失败', e)
}
删除数据
js
// 异步删除单个key
wx.removeStorage({
key: 'tempData',
success: () => console.log('临时数据已清除')
})
// 同步删除
try {
wx.removeStorageSync('obsoleteKey')
console.log('旧数据已删除')
} catch (e) {
console.error('删除失败', e)
}
清空数据
js
// 异步清空所有存储
wx.clearStorage({
success: () => console.log('所有本地数据已清除')
})
// 同步清空 - 用户退出登录时
function logout() {
try {
wx.clearStorageSync()
console.log('存储已清空,跳转到登录页')
wx.reLaunch({ url: '/pages/login/login' })
} catch (e) {
console.error('清空存储失败', e)
}
}
上拉刷新与下拉加载
scroll-view 组件
scroll-view 是一个可滚动的容器组件,需要手动指定高度。通过其内置事件实现滚动行为:
上拉加载更多
属性:
bindscrolltolower
:滚动到底部/右边时触发。lower-threshold
:触发底部的距离阈值(单位:px/rpx)。
示例:
html<scroll-view scroll-y lower-threshold="100" bindscrolltolower="loadMore" style="height: 500rpx" > <!-- 内容 --> </scroll-view>
下拉刷新
属性:
refresher-enabled
:开启自定义下拉刷新。refresher-triggered
:控制刷新状态。bindrefresherrefresh
:下拉刷新时触发。
示例:
html<scroll-view scroll-y refresher-enabled refresher-triggered="{{isRefreshing}}" bindrefresherrefresh="onRefresh" style="height: 500rpx" > <!-- 内容 --> </scroll-view>
TIP
✅ 优点:
- 局部控制:适用于页面中的特定滚动区域(非全屏)。
- 灵活性强:可自定义刷新动画和加载行为。
❌ 缺点:
- 需固定高度:必须设置明确的高度(如 height: 100vh 或固定值)。
- 无法覆盖导航栏:下拉刷新的动画仅在 scroll-view 内部生效。
页面原生事件
通过页面配置文件(.json)和生命周期函数实现全局页面的滚动行为。
上拉加载更多
配置项:
json{ "onReachBottomDistance": 100 // 距离底部100rpx时触发 }
- 生命周期函数:
jsonReachBottom() { // 加载更多逻辑 }
下拉刷新
配置项:
json{ "enablePullDownRefresh": true // 开启全局下拉刷新 }
- 生命周期函数:
jsonPullDownRefresh() { // 刷新逻辑 wx.stopPullDownRefresh() // 手动停止刷新动画 }
TIP
✅ 优点:
- 简单易用:无需处理滚动容器,天然支持整个页面。
- 全局覆盖:下拉刷新动画覆盖整个页面(包括导航栏)。
- 无高度限制:自动适配页面高度。
❌ 缺点:
- 无法局部控制:只适用于整个页面的滚动区域。
- 刷新动画固定:无法完全自定义下拉刷新样式(仅支持全局配置)。
强制更新
js
App({
onLaunch() {
// 1. 获取更新管理器
const updateManager = wx.getUpdateManager();
updateManager.onCheckForUpdate(res => {
if (res.hasUpdate) {
wx.showToast({
title: '正在下载新版本',
icon: 'loading'
});
}
});
// 显示下载进度(增强用户体验)
updateManager.onProgressUpdate((res) => {
console.log('下载进度', res.progress);
wx.showToast({
title: `下载中... ${res.progress}%`,
icon: 'none'
});
});
// 2. 正确设置监听器
updateManager.onUpdateReady = () => {
wx.showModal({
title: '更新提示',
content: '新版本已准备就绪',
success(res) {
if (res.confirm) {
// 应用更新
updateManager.applyUpdate();
}
}
});
};
// 3. 添加失败监听器(关键补充)
updateManager.onUpdateFailed = () => {
wx.showModal({
title: '更新失败',
content: '新版本下载失败',
showCancel: false
});
};
}
})
其他
转发给朋友
js
Page({
onShareAppMessage() {
return {
title: '自定义标题', // 必填
path: '/pages/index?id=123', // 携带参数
imageUrl: 'https://example.com/share.jpg' // 建议比例 5:4
}
}
})
分享到朋友圈
js
Page({
onShareTimeline() {
return {
title: '朋友圈标题', // 最多12字
query: 'id=123', // 通过页面onLoad接收
imageUrl: 'https://example.com/timeline.jpg'
}
}
})
获取微信头像
html
<!-- 页面内添加头像选择按钮 -->
<button open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
<image mode="aspectFill" src="{{avatarUrl}}"></image>
</button>
js
Page({
data: {
avatarUrl: '/images/default-avatar.png'
},
onChooseAvatar(e) {
const { avatarUrl } = e.detail
wx.uploadFile({
url: 'https://your.domain.com/upload', // 服务器接口
filePath: avatarUrl,
name: 'avatar',
success: (res) => {
// 获取服务器返回的头像URL
const serverAvatar = JSON.parse(res.data).url
this.setData({ avatarUrl: serverAvatar })
},
fail: (err) => {
wx.showToast({ title: '上传失败', icon: 'error' })
}
})
}
})
获取微信昵称
html
<!-- WXML文件 -->
<input
type="nickname"
placeholder="请输入昵称"
value="{{nickname}}"
bindinput="handleNicknameInput"
/>
js
// JS文件
Page({
data: {
nickname: '' // 存储用户昵称
},
// 处理昵称输入框失焦事件
handleNicknameInput(e) {
const nickname = e.detail.value.trim();
// 保存昵称
this.setData({ nickname });
// 发送到服务器
this.saveNicknameToServer(nickname);
},
// 保存昵称到服务器
saveNicknameToServer(nickname) {
wx.request({
url: 'https://your-api.com/save-nickname',
method: 'POST',
data: {
nickname,
openid: wx.getStorageSync('openid')
},
success: (res) => {
if (res.data.code === 200) {
wx.setStorageSync('nickname', nickname);
}
}
});
}
})
获取手机号
html
<button open-type="getPhoneNumber" bindgetphonenumber="handleGetPhoneNumber">
授权获取手机号
</button>
js
Page({
data: {
phoneLoading: false
},
onGetPhoneNumber(e) {
const { code } = e.detail;
// 用户拒绝授权处理
if (e.detail.errMsg === "getPhoneNumber:fail user deny") {
wx.showToast({ title: '您已拒绝授权', icon: 'none' });
return;
}
// 防止重复提交
if (this.data.phoneLoading) return;
this.setData({ phoneLoading: true });
wx.request({
url: 'https://your-api.com/get-user-phone',
method: 'POST',
data: { code }, // 传递微信返回的code
success: (res) => {
if (res.data.code === 200) {
const phone = res.data.phone;
wx.setStorageSync('userPhone', phone);
this.handlePhoneSuccess(phone);
} else {
this.showPhoneError();
}
},
fail: () => {
this.showPhoneError();
},
complete: () => {
this.setData({ phoneLoading: false });
}
});
},
handlePhoneSuccess(phone) {
wx.showModal({
title: '手机号获取成功',
content: `您的手机号:${phone}`,
showCancel: false
});
},
showPhoneError() {
wx.showToast({
title: '手机号获取失败',
icon: 'error',
duration: 3000
});
}
})