概述
微信小程序是一种轻量级的应用程序,用户可以在微信内直接使用,无需下载安装。小程序提供了接近原生应用的用户体验,同时具有便捷的分享和传播特性。
核心特性
- 即用即走: 无需安装,扫码即可使用
- 接近原生: 提供接近原生应用的用户体验
- 丰富的API: 提供丰富的微信能力和设备能力
- 便捷分享: 可以方便地分享给好友或群聊
- 统一管理: 通过微信统一管理和分发
技术架构
| 层级 | 技术 | 说明 |
|---|---|---|
| 视图层 | WXML + WXSS | 页面结构和样式 |
| 逻辑层 | JavaScript | 业务逻辑处理 |
| 配置层 | JSON | 页面和应用配置 |
| 原生组件 | 微信提供 | 地图、视频、输入框等 |
开发工具
| 工具 | 用途 | 特点 |
|---|---|---|
| 微信开发者工具 | 代码编辑、调试、预览 | 官方工具,功能完整 |
| 真机调试 | 真实设备测试 | 更接近用户使用环境 |
| 云开发 | 后端服务 | 无需搭建服务器 |
💡 优势: 微信小程序结合了Web开发的便捷性和原生应用的用户体验。
环境准备
开发者账号注册
-
注册微信小程序账号:
- 访问 微信公众平台
- 选择”小程序”类型进行注册
- 完成邮箱验证和信息填写
-
获取 AppID:
- 登录小程序管理后台
- 在”设置” -> “开发设置”中查看 AppID
- AppID 用于开发工具的项目创建
开发工具安装
-
下载微信开发者工具:
- 访问 微信开发者工具官网
- 选择对应操作系统版本下载
- 安装并启动开发者工具
-
创建项目:
1. 打开微信开发者工具 2. 点击"新建项目" 3. 填入项目目录、AppID 和项目名称 4. 选择"不使用云服务"(初学者) 5. 点击"新建"完成项目创建
项目结构
小程序项目/
├── pages/ # 页面文件夹
│ ├── index/ # 首页
│ │ ├── index.js # 页面逻辑
│ │ ├── index.json # 页面配置
│ │ ├── index.wxml # 页面结构
│ │ └── index.wxss # 页面样式
│ └── logs/ # 其他页面
├── utils/ # 工具函数
├── app.js # 小程序逻辑
├── app.json # 全局配置
├── app.wxss # 全局样式
└── project.config.json # 项目配置
WXSS样式
WXSS(WeiXin Style Sheets)是微信小程序的样式语言,基于 CSS 进行扩展。
与 CSS 的区别
| 特性 | WXSS | CSS |
|---|---|---|
| 尺寸单位 | rpx(响应式像素) | px, em, rem |
| 样式导入 | @import “path” | @import url() |
| 选择器 | 部分支持 | 完全支持 |
| 适配性 | 自动适配不同设备 | 需要媒体查询 |
rpx 尺寸单位
rpx(responsive pixel) 是微信小程序独有的尺寸单位,用于解决屏幕适配问题。
实现原理
- 规定所有设备屏幕宽度为 750rpx
- 在不同设备上,1rpx 对应不同的物理像素
- 小程序运行时自动进行换算
换算关系
在 iPhone 6 上:
- 750rpx = 375px = 750物理像素
- 1rpx = 0.5px = 1物理像素
| 设备 | rpx换算px | px换算rpx |
|---|---|---|
| iPhone 5 | 1rpx = 0.42px | 1px = 2.34rpx |
| iPhone 6 | 1rpx = 0.5px | 1px = 2rpx |
| iPhone 6 Plus | 1rpx = 0.552px | 1px = 1.81rpx |
使用示例
/* 推荐:使用 rpx 进行适配 */
.container {
width: 750rpx; /* 全屏宽度 */
height: 300rpx; /* 固定高度 */
padding: 20rpx; /* 内边距 */
margin: 10rpx; /* 外边距 */
font-size: 28rpx; /* 字体大小 */
}
/* 在 iPhone 6 上的设计稿换算 */
.box {
width: 200rpx; /* 设计稿 100px */
height: 100rpx; /* 设计稿 50px */
border-radius: 10rpx; /* 设计稿 5px */
}
样式导入
使用 @import 可以导入外部样式文件:
1. 创建公共样式文件
/* styles/common.wxss */
.btn {
padding: 20rpx 40rpx;
border-radius: 10rpx;
color: #fff;
text-align: center;
}
.btn-primary {
background-color: #007aff;
}
.btn-success {
background-color: #4cd964;
}
.btn-warning {
background-color: #ff9500;
}
2. 导入样式文件
/* pages/index/index.wxss */
@import "../../styles/common.wxss";
.page-container {
padding: 20rpx;
background-color: #f5f5f5;
}
.custom-button {
margin-top: 20rpx;
}
全局样式和局部样式
全局样式(app.wxss)
/* app.wxss - 作用于所有页面 */
page {
background-color: #f8f8f8;
font-size: 28rpx;
color: #333;
line-height: 1.5;
}
/* 通用容器样式 */
.container {
padding: 20rpx;
min-height: 100vh;
box-sizing: border-box;
}
/* 通用文本样式 */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
/* 通用颜色类 */
.text-primary { color: #007aff; }
.text-success { color: #4cd964; }
.text-warning { color: #ff9500; }
.text-danger { color: #ff3b30; }
局部样式(页面.wxss)
/* pages/index/index.wxss - 仅作用于当前页面 */
.hero-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 80rpx 40rpx;
text-align: center;
}
.hero-title {
font-size: 48rpx;
font-weight: bold;
margin-bottom: 20rpx;
}
.hero-subtitle {
font-size: 28rpx;
opacity: 0.9;
}
样式优先级
- 内联样式 > 局部样式 > 全局样式
- 相同优先级时,后定义的覆盖先定义的
- 使用 !important 可以提高优先级(谨慎使用)
全局配置
全局配置文件 app.json 用于配置小程序的全局设置。
app.json 结构
{
"pages": [
"pages/index/index",
"pages/profile/profile",
"pages/settings/settings"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "我的小程序",
"navigationBarTextStyle": "black",
"backgroundColor": "#f8f8f8",
"enablePullDownRefresh": false,
"onReachBottomDistance": 50
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "icon/home.png",
"selectedIconPath": "icon/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/profile/profile",
"iconPath": "icon/profile.png",
"selectedIconPath": "icon/profile-active.png",
"text": "我的"
}
]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": false
}
window 配置详解
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色 |
| navigationBarTextStyle | String | white | 导航栏标题颜色,仅支持 black/white |
| navigationBarTitleText | String | 导航栏标题文字内容 | |
| backgroundColor | HexColor | #ffffff | 窗口的背景色 |
| backgroundTextStyle | String | dark | 下拉 loading 的样式,仅支持 dark/light |
| enablePullDownRefresh | Boolean | false | 是否开启当前页面下拉刷新 |
| onReachBottomDistance | Number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为px |
tabBar 配置
基础配置
{
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"position": "bottom",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "icon/home.png",
"selectedIconPath": "icon/home-active.png",
"text": "首页"
}
]
}
}
完整的 tabBar 示例
{
"tabBar": {
"color": "#999999",
"selectedColor": "#FF6B4A",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"position": "bottom",
"list": [
{
"pagePath": "pages/home/home",
"iconPath": "images/tabbar/home.png",
"selectedIconPath": "images/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/category/category",
"iconPath": "images/tabbar/category.png",
"selectedIconPath": "images/tabbar/category-active.png",
"text": "分类"
},
{
"pagePath": "pages/cart/cart",
"iconPath": "images/tabbar/cart.png",
"selectedIconPath": "images/tabbar/cart-active.png",
"text": "购物车"
},
{
"pagePath": "pages/profile/profile",
"iconPath": "images/tabbar/profile.png",
"selectedIconPath": "images/tabbar/profile-active.png",
"text": "我的"
}
]
}
}
网络数据请求
小程序提供了 wx.request() API 来发起网络请求。
域名配置
在小程序管理后台配置合法域名:
- 登录小程序管理后台
- 进入”开发” -> “开发管理” -> “开发设置”
- 在”服务器域名”中添加 request 合法域名
基本用法
GET 请求
// 获取用户列表
function getUserList() {
wx.request({
url: 'https://api.example.com/users',
method: 'GET',
data: {
page: 1,
limit: 10
},
header: {
'Content-Type': 'application/json'
},
success(res) {
console.log('请求成功', res.data)
// 处理成功响应
},
fail(err) {
console.error('请求失败', err)
// 处理失败情况
},
complete() {
console.log('请求完成')
// 无论成功失败都会执行
}
})
}
POST 请求
// 用户登录
function userLogin(username, password) {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://api.example.com/auth/login',
method: 'POST',
data: {
username: username,
password: password
},
header: {
'Content-Type': 'application/json'
},
success(res) {
if (res.statusCode === 200) {
resolve(res.data)
} else {
reject(new Error('登录失败'))
}
},
fail(err) {
reject(err)
}
})
})
}
// 使用示例
userLogin('admin', '123456')
.then(data => {
console.log('登录成功', data)
// 保存token
wx.setStorageSync('token', data.token)
})
.catch(err => {
console.error('登录失败', err)
wx.showToast({
title: '登录失败',
icon: 'error'
})
})
封装网络请求
创建一个通用的网络请求工具:
// utils/request.js
const BASE_URL = 'https://api.example.com'
function request(options) {
return new Promise((resolve, reject) => {
// 显示加载提示
wx.showLoading({
title: '加载中...',
mask: true
})
wx.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'Content-Type': 'application/json',
'Authorization': wx.getStorageSync('token') || '',
...options.header
},
success(res) {
wx.hideLoading()
if (res.statusCode === 200) {
resolve(res.data)
} else if (res.statusCode === 401) {
// token 过期,跳转到登录页
wx.removeStorageSync('token')
wx.navigateTo({
url: '/pages/login/login'
})
reject(new Error('登录已过期'))
} else {
reject(new Error(`请求失败: ${res.statusCode}`))
}
},
fail(err) {
wx.hideLoading()
wx.showToast({
title: '网络错误',
icon: 'error'
})
reject(err)
}
})
})
}
// 导出具体的请求方法
module.exports = {
get(url, data) {
return request({
url,
method: 'GET',
data
})
},
post(url, data) {
return request({
url,
method: 'POST',
data
})
},
put(url, data) {
return request({
url,
method: 'PUT',
data
})
},
delete(url, data) {
return request({
url,
method: 'DELETE',
data
})
}
}
使用封装的请求工具:
// 页面中使用
const api = require('../../utils/request')
Page({
data: {
userList: []
},
async onLoad() {
await this.loadUserList()
},
async loadUserList() {
try {
const result = await api.get('/users', {
page: 1,
limit: 20
})
this.setData({
userList: result.data
})
} catch (error) {
console.error('加载用户列表失败', error)
}
},
async createUser(userData) {
try {
await api.post('/users', userData)
wx.showToast({
title: '创建成功',
icon: 'success'
})
// 重新加载列表
await this.loadUserList()
} catch (error) {
console.error('创建用户失败', error)
}
}
})
页面导航
小程序提供了声明式导航和编程式导航两种方式。
声明式导航
使用 navigator 组件进行页面跳转:
<!-- 跳转到普通页面 -->
<navigator url="/pages/detail/detail" open-type="navigate">
查看详情
</navigator>
<!-- 跳转到 tabBar 页面 -->
<navigator url="/pages/home/home" open-type="switchTab">
返回首页
</navigator>
<!-- 返回上一页 -->
<navigator open-type="navigateBack" delta="1">
返回
</navigator>
<!-- 带参数跳转 -->
<navigator url="/pages/detail/detail?id=123&type=product">
查看商品详情
</navigator>
编程式导航
使用 wx API 进行页面跳转:
基本跳转方法
Page({
// 跳转到普通页面
navigateToDetail() {
wx.navigateTo({
url: '/pages/detail/detail?id=123'
})
},
// 跳转到 tabBar 页面
switchToHome() {
wx.switchTab({
url: '/pages/home/home'
})
},
// 重定向到页面(关闭当前页面)
redirectToLogin() {
wx.redirectTo({
url: '/pages/login/login'
})
},
// 重新启动到页面(关闭所有页面)
reLaunchToIndex() {
wx.reLaunch({
url: '/pages/index/index'
})
},
// 返回上一页
goBack() {
wx.navigateBack({
delta: 1
})
}
})
带参数跳转
// 跳转时传递参数
goToUserProfile(userId) {
wx.navigateTo({
url: `/pages/profile/profile?userId=${userId}&from=list`
})
}
// 接收参数的页面
Page({
onLoad(options) {
const { userId, from } = options
console.log('用户ID:', userId)
console.log('来源页面:', from)
// 根据参数加载数据
this.loadUserData(userId)
},
loadUserData(userId) {
// 加载用户数据
}
})
封装导航工具
// utils/navigation.js
module.exports = {
// 跳转到页面
navigateTo(url, params = {}) {
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&')
const fullUrl = queryString ? `${url}?${queryString}` : url
wx.navigateTo({
url: fullUrl,
fail(err) {
console.error('页面跳转失败', err)
}
})
},
// 跳转到 tabBar
switchTab(url) {
wx.switchTab({
url,
fail(err) {
console.error('tabBar跳转失败', err)
}
})
},
// 返回上一页
goBack(delta = 1) {
wx.navigateBack({ delta })
},
// 确认后返回
confirmGoBack(message = '确定要返回吗?') {
wx.showModal({
title: '提示',
content: message,
success: (res) => {
if (res.confirm) {
this.goBack()
}
}
})
}
}
生命周期
小程序包含应用生命周期和页面生命周期。
应用生命周期
在 app.js 中定义:
// app.js
App({
// 小程序初始化完成时触发(全局只触发一次)
onLaunch(options) {
console.log('小程序启动', options)
// 检查更新
this.checkUpdate()
// 获取用户信息
this.getUserInfo()
},
// 小程序启动,或从后台进入前台显示时触发
onShow(options) {
console.log('小程序显示', options)
// 刷新数据
this.refreshData()
},
// 小程序从前台进入后台时触发
onHide() {
console.log('小程序隐藏')
// 保存数据
this.saveData()
},
// 小程序发生脚本错误或API调用报错时触发
onError(error) {
console.error('小程序错误', error)
// 错误上报
this.reportError(error)
},
// 页面不存在时触发
onPageNotFound(res) {
console.log('页面不存在', res)
wx.redirectTo({
url: '/pages/index/index'
})
},
// 检查更新
checkUpdate() {
if (wx.canIUse('getUpdateManager')) {
const updateManager = wx.getUpdateManager()
updateManager.onCheckForUpdate((res) => {
console.log('检查更新结果', res.hasUpdate)
})
updateManager.onUpdateReady(() => {
wx.showModal({
title: '更新提示',
content: '新版本已准备好,是否重启应用?',
success: (res) => {
if (res.confirm) {
updateManager.applyUpdate()
}
}
})
})
updateManager.onUpdateFailed(() => {
console.error('更新失败')
})
}
},
globalData: {
userInfo: null,
token: null
}
})
页面生命周期
// pages/index/index.js
Page({
data: {
loading: true,
list: []
},
// 页面加载时触发,一个页面只会调用一次
onLoad(options) {
console.log('页面加载', options)
// 获取页面参数
const { id, type } = options
// 设置页面标题
wx.setNavigationBarTitle({
title: '页面标题'
})
// 初始化数据
this.initData()
},
// 页面显示/切入前台时触发
onShow() {
console.log('页面显示')
// 刷新数据
this.refreshData()
},
// 页面初次渲染完成时触发,一个页面只会调用一次
onReady() {
console.log('页面渲染完成')
// 获取组件实例
this.selectComponent('#my-component')
},
// 页面隐藏/切入后台时触发
onHide() {
console.log('页面隐藏')
// 暂停音视频
// 保存表单数据
},
// 页面卸载时触发
onUnload() {
console.log('页面卸载')
// 清理定时器
if (this.timer) {
clearInterval(this.timer)
}
// 清理事件监听
wx.off('customEvent', this.handleCustomEvent)
},
// 下拉刷新时触发
onPullDownRefresh() {
console.log('下拉刷新')
this.refreshData().finally(() => {
wx.stopPullDownRefresh()
})
},
// 上拉触底时触发
onReachBottom() {
console.log('上拉触底')
this.loadMore()
},
// 页面滚动时触发
onPageScroll(e) {
// console.log('页面滚动', e.scrollTop)
// 控制返回顶部按钮显示
this.setData({
showBackTop: e.scrollTop > 500
})
},
// 用户点击右上角分享
onShareAppMessage() {
return {
title: '分享标题',
path: '/pages/index/index',
imageUrl: '/images/share.png'
}
},
// 初始化数据
async initData() {
try {
this.setData({ loading: true })
const data = await this.fetchData()
this.setData({
list: data,
loading: false
})
} catch (error) {
console.error('初始化数据失败', error)
this.setData({ loading: false })
}
},
// 刷新数据
async refreshData() {
try {
const data = await this.fetchData()
this.setData({ list: data })
} catch (error) {
console.error('刷新数据失败', error)
}
},
// 加载更多
async loadMore() {
if (this.data.loading) return
try {
this.setData({ loading: true })
const newData = await this.fetchData(this.data.list.length)
this.setData({
list: [...this.data.list, ...newData],
loading: false
})
} catch (error) {
console.error('加载更多失败', error)
this.setData({ loading: false })
}
}
})
实战案例
本地生活小程序
这是一个综合性的实战案例,展示小程序开发的核心技术。
功能需求
- 首页轮播图展示
- 九宫格导航
- 商家列表展示
- 下拉刷新和上拉加载
- 页面跳转和参数传递
项目结构
local-life/
├── pages/
│ ├── index/ # 首页
│ ├── category/ # 分类页
│ ├── detail/ # 详情页
│ └── my/ # 我的页面
├── components/ # 自定义组件
├── utils/ # 工具函数
├── images/ # 图片资源
└── styles/ # 公共样式
首页实现
index.wxml:
<view class="container">
<!-- 轮播图 -->
<swiper class="banner-swiper" indicator-dots autoplay circular>
<swiper-item wx:for="{{banners}}" wx:key="id">
<image src="{{item.image}}" mode="aspectFill" bindtap="onBannerTap" data-url="{{item.url}}" />
</swiper-item>
</swiper>
<!-- 九宫格导航 -->
<view class="grid-nav">
<view wx:for="{{categories}}" wx:key="id" class="grid-item" bindtap="onCategoryTap" data-id="{{item.id}}">
<image src="{{item.icon}}" class="grid-icon" />
<text class="grid-text">{{item.name}}</text>
</view>
</view>
<!-- 商家列表 -->
<view class="shop-list">
<view class="section-title">推荐商家</view>
<view wx:for="{{shops}}" wx:key="id" class="shop-item" bindtap="onShopTap" data-id="{{item.id}}">
<image src="{{item.image}}" class="shop-image" />
<view class="shop-info">
<text class="shop-name">{{item.name}}</text>
<text class="shop-desc">{{item.description}}</text>
<view class="shop-meta">
<text class="shop-rating">⭐ {{item.rating}}</text>
<text class="shop-distance">{{item.distance}}km</text>
</view>
</view>
</view>
</view>
<!-- 加载提示 -->
<view wx:if="{{loading}}" class="loading">
<text>加载中...</text>
</view>
</view>
index.js:
const api = require('../../utils/request')
Page({
data: {
banners: [],
categories: [],
shops: [],
loading: false,
page: 1,
hasMore: true
},
async onLoad() {
await this.initData()
},
async onPullDownRefresh() {
await this.refreshData()
wx.stopPullDownRefresh()
},
async onReachBottom() {
if (!this.data.hasMore || this.data.loading) return
await this.loadMoreShops()
},
// 初始化数据
async initData() {
try {
this.setData({ loading: true })
const [banners, categories, shops] = await Promise.all([
api.get('/banners'),
api.get('/categories'),
api.get('/shops', { page: 1, limit: 10 })
])
this.setData({
banners: banners.data,
categories: categories.data,
shops: shops.data,
loading: false,
page: 1,
hasMore: shops.data.length === 10
})
} catch (error) {
console.error('初始化数据失败', error)
this.setData({ loading: false })
}
},
// 刷新数据
async refreshData() {
this.setData({ page: 1 })
await this.initData()
},
// 加载更多商家
async loadMoreShops() {
try {
this.setData({ loading: true })
const nextPage = this.data.page + 1
const result = await api.get('/shops', {
page: nextPage,
limit: 10
})
this.setData({
shops: [...this.data.shops, ...result.data],
page: nextPage,
hasMore: result.data.length === 10,
loading: false
})
} catch (error) {
console.error('加载更多失败', error)
this.setData({ loading: false })
}
},
// 轮播图点击
onBannerTap(e) {
const { url } = e.currentTarget.dataset
if (url) {
wx.navigateTo({ url })
}
},
// 分类点击
onCategoryTap(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({
url: `/pages/category/category?id=${id}`
})
},
// 商家点击
onShopTap(e) {
const { id } = e.currentTarget.dataset
wx.navigateTo({
url: `/pages/detail/detail?shopId=${id}`
})
},
// 分享
onShareAppMessage() {
return {
title: '本地生活服务',
path: '/pages/index/index'
}
}
})
index.wxss:
.container {
background-color: #f5f5f5;
}
/* 轮播图样式 */
.banner-swiper {
width: 100%;
height: 300rpx;
}
.banner-swiper image {
width: 100%;
height: 100%;
}
/* 九宫格导航 */
.grid-nav {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20rpx;
padding: 30rpx;
background-color: #fff;
margin-bottom: 20rpx;
}
.grid-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx;
}
.grid-icon {
width: 80rpx;
height: 80rpx;
margin-bottom: 10rpx;
}
.grid-text {
font-size: 26rpx;
color: #333;
}
/* 商家列表 */
.shop-list {
background-color: #fff;
padding: 0 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
padding: 30rpx 0 20rpx;
color: #333;
}
.shop-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.shop-image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
margin-right: 20rpx;
}
.shop-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.shop-name {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 10rpx;
}
.shop-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 10rpx;
}
.shop-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.shop-rating {
font-size: 24rpx;
color: #ff6b35;
}
.shop-distance {
font-size: 24rpx;
color: #999;
}
/* 加载提示 */
.loading {
text-align: center;
padding: 40rpx;
color: #999;
}
性能优化
1. 代码优化
避免频繁的 setData
// ❌ 不好的做法
for (let i = 0; i < 100; i++) {
this.setData({
[`list[${i}]`]: newData[i]
})
}
// ✅ 好的做法
const updates = {}
for (let i = 0; i < 100; i++) {
updates[`list[${i}]`] = newData[i]
}
this.setData(updates)
// ✅ 更好的做法
this.setData({
list: newData
})
减少数据传输量
// ❌ 传输不必要的数据
this.setData({
userInfo: {
id: 1,
name: 'John',
email: 'john@example.com',
avatar: 'http://example.com/avatar.jpg',
// 大量其他不需要的字段...
unnecessary_field_1: '...',
unnecessary_field_2: '...'
}
})
// ✅ 只传输需要的数据
const { id, name, avatar } = userInfo
this.setData({
userInfo: { id, name, avatar }
})
2. 图片优化
图片懒加载
<image
wx:for="{{imageList}}"
wx:key="id"
src="{{item.url}}"
lazy-load="{{true}}"
mode="aspectFill" />
合理的图片格式和大小
// 根据设备像素比选择合适的图片
const pixelRatio = wx.getSystemInfoSync().pixelRatio
const imageSize = pixelRatio > 2 ? '@3x' : pixelRatio > 1 ? '@2x' : ''
const imageUrl = `https://example.com/image${imageSize}.jpg`
3. 网络优化
请求合并
// ❌ 多个独立请求
const user = await api.get('/user')
const posts = await api.get('/posts')
const comments = await api.get('/comments')
// ✅ 合并请求
const [user, posts, comments] = await Promise.all([
api.get('/user'),
api.get('/posts'),
api.get('/comments')
])
缓存策略
// utils/cache.js
class Cache {
constructor() {
this.cache = new Map()
this.expire = new Map()
}
set(key, value, ttl = 5 * 60 * 1000) { // 默认5分钟
this.cache.set(key, value)
this.expire.set(key, Date.now() + ttl)
}
get(key) {
if (this.expire.get(key) < Date.now()) {
this.cache.delete(key)
this.expire.delete(key)
return null
}
return this.cache.get(key)
}
async getOrFetch(key, fetchFn, ttl) {
let data = this.get(key)
if (!data) {
data = await fetchFn()
this.set(key, data, ttl)
}
return data
}
}
const cache = new Cache()
// 使用缓存
async function getUserInfo(userId) {
return await cache.getOrFetch(
`user_${userId}`,
() => api.get(`/users/${userId}`),
10 * 60 * 1000 // 10分钟缓存
)
}
4. 长列表优化
使用虚拟列表减少DOM节点:
<!-- 使用 recycle-view 组件 -->
<recycle-view batch="{{batchSetRecycleData}}" id="recycleId">
<recycle-item wx:for="{{recycleList}}" wx:key="id">
<view class="item">{{item.name}}</view>
</recycle-item>
</recycle-view>
最佳实践
1. 代码组织
模块化开发
// utils/validator.js - 表单验证工具
module.exports = {
isEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
},
isPhone(phone) {
return /^1[3-9]\d{9}$/.test(phone)
},
isRequired(value) {
return value !== null && value !== undefined && value !== ''
}
}
// utils/format.js - 格式化工具
module.exports = {
formatDate(date, format = 'YYYY-MM-DD') {
// 日期格式化逻辑
},
formatNumber(num) {
return num.toLocaleString()
},
formatFileSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB']
if (bytes === 0) return '0 Bytes'
const i = Math.floor(Math.log(bytes) / Math.log(1024))
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
}
}
组件化开发
// components/loading/loading.js
Component({
properties: {
show: {
type: Boolean,
value: false
},
text: {
type: String,
value: '加载中...'
}
},
data: {
// 组件内部数据
},
methods: {
// 组件方法
}
})
2. 错误处理
全局错误处理
// app.js
App({
onError(error) {
console.error('全局错误:', error)
// 错误上报
this.reportError(error)
// 用户提示
wx.showToast({
title: '系统繁忙,请稍后重试',
icon: 'none'
})
},
reportError(error) {
// 发送错误信息到服务器
wx.request({
url: 'https://api.example.com/errors',
method: 'POST',
data: {
error: error.toString(),
stack: error.stack,
userAgent: wx.getSystemInfoSync(),
timestamp: Date.now()
}
})
}
})
页面错误边界
Page({
data: {
hasError: false,
errorMessage: ''
},
async onLoad() {
try {
await this.initData()
} catch (error) {
this.handleError(error)
}
},
handleError(error) {
console.error('页面错误:', error)
this.setData({
hasError: true,
errorMessage: '页面加载失败,请重试'
})
},
onRetry() {
this.setData({
hasError: false,
errorMessage: ''
})
this.onLoad()
}
})
3. 用户体验优化
Loading 状态管理
// utils/loading.js
class LoadingManager {
constructor() {
this.loadingCount = 0
}
show(title = '加载中...') {
if (this.loadingCount === 0) {
wx.showLoading({ title, mask: true })
}
this.loadingCount++
}
hide() {
this.loadingCount--
if (this.loadingCount <= 0) {
this.loadingCount = 0
wx.hideLoading()
}
}
}
const loadingManager = new LoadingManager()
module.exports = loadingManager
防抖和节流
// utils/debounce.js
function debounce(func, wait) {
let timeout
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout)
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
function throttle(func, limit) {
let inThrottle
return function() {
const args = arguments
const context = this
if (!inThrottle) {
func.apply(context, args)
inThrottle = true
setTimeout(() => inThrottle = false, limit)
}
}
}
module.exports = { debounce, throttle }
常见问题
问题1:页面跳转失败
❓ 现象: 使用
wx.navigateTo跳转页面失败
可能原因和解决方案:
-
页面路径错误:
// ❌ 错误的路径 wx.navigateTo({ url: 'pages/detail/detail' // 缺少开头的 / }) // ✅ 正确的路径 wx.navigateTo({ url: '/pages/detail/detail' }) -
页面栈层级过深:
// 页面栈最多10层,超出后需要使用 redirectTo wx.redirectTo({ url: '/pages/detail/detail' }) -
跳转到 tabBar 页面:
// ❌ 错误:用 navigateTo 跳转 tabBar 页面 wx.navigateTo({ url: '/pages/home/home' }) // ✅ 正确:用 switchTab 跳转 tabBar 页面 wx.switchTab({ url: '/pages/home/home' })
问题2:数据不更新
❓ 现象: 调用 setData 后页面数据没有更新
解决方案:
-
检查数据路径:
// ❌ 错误的数组更新方式 this.data.list.push(newItem) this.setData({ list: this.data.list }) // ✅ 正确的数组更新方式 this.setData({ list: [...this.data.list, newItem] }) -
深层对象更新:
// ❌ 错误:直接修改对象属性 this.data.user.name = 'New Name' this.setData({ user: this.data.user }) // ✅ 正确:使用对象展开或指定路径 this.setData({ 'user.name': 'New Name' }) // 或者 this.setData({ user: { ...this.data.user, name: 'New Name' } })
问题3:网络请求失败
❓ 现象: wx.request 请求返回失败
解决方案:
-
检查域名配置:
- 确保请求域名已在小程序管理后台配置
- 域名必须是 HTTPS 协议
-
开发环境跳过域名校验:
// 在开发者工具中勾选"不校验合法域名" -
处理跨域问题:
// 小程序不存在跨域问题,但服务器需要正确处理 OPTIONS 请求
问题4:图片加载失败
❓ 现象: 图片无法显示或加载很慢
解决方案:
-
使用合适的 mode 属性:
<!-- 保持比例完整显示 --> <image src="{{imageUrl}}" mode="aspectFit" /> <!-- 保持比例裁剪填充 --> <image src="{{imageUrl}}" mode="aspectFill" /> -
添加默认图片和错误处理:
<image src="{{imageUrl}}" mode="aspectFill" binderror="onImageError" class="image" />onImageError(e) { const { index } = e.currentTarget.dataset this.setData({ [`imageList[${index}].url`]: '/images/default.png' }) } -
使用图片懒加载:
<image src="{{imageUrl}}" lazy-load="{{true}}" mode="aspectFill" />
问题5:性能问题
❓ 现象: 页面卡顿、响应慢
解决方案:
-
优化 setData 使用:
// ❌ 频繁调用 setData for (let i = 0; i < 1000; i++) { this.setData({ [`list[${i}]`]: data[i] }) } // ✅ 批量更新 this.setData({ list: data }) -
避免在页面滚动时频繁更新:
// ❌ 每次滚动都更新 onPageScroll(e) { this.setData({ scrollTop: e.scrollTop }) } // ✅ 使用节流 onPageScroll: throttle(function(e) { this.setData({ scrollTop: e.scrollTop }) }, 100)
相关文章
基础开发
- Android界面控件详解 - 移动端UI组件对比
移动开发技术
- 微信公众号开发 - 微信生态开发
开发工具
- Git版本控制 - 代码版本管理
- 微信开发者工具使用 - 官方开发环境
- 小程序调试技巧 - 开发调试方法
数据库集成
- MySQL数据库 - 关系型数据库
- MongoDB文档数据库 - NoSQL数据库
- Redis缓存 - 内存数据库
部署运维
- Docker容器化 - 应用容器化
总结
微信小程序作为移动端轻量级应用的重要形式,为开发者提供了便捷的开发体验和丰富的功能特性。
🎯 核心优势
- 开发简便: 基于Web技术栈,学习成本低
- 生态丰富: 微信生态支持,用户触达便捷
- 性能优异: 接近原生应用的使用体验
- 功能完整: 丰富的API和组件支持
🛠️ 技术要点
- WXML模板: 数据绑定、条件渲染、列表渲染
- WXSS样式: rpx适配单位、样式导入
- JavaScript逻辑: 页面生命周期、事件处理
- 组件化开发: 自定义组件、组件通信
- 网络请求: 数据获取、状态管理
- 页面导航: 路由跳转、参数传递
🚀 最佳实践
- 性能优化: 合理使用setData,优化渲染
- 用户体验: 加载状态、错误处理、交互反馈
- 代码质量: 模块化开发、组件复用、错误处理
- 项目管理: 版本控制、代码规范、团队协作
相关文章
移动开发技术
- Android 移动应用开发 - 原生安卓开发
- 移动开发技术对比 - 技术选型指南
实战项目
- 保持对新技术的敏感度