Appearance
章节 1:小程序基础入门
学习目标
- 理解微信小程序的双线程架构与运行原理
- 掌握 WXML + WXSS 的基本语法与数据绑定
- 学会事件绑定、条件渲染与列表渲染
- 掌握页面路由配置与导航传参
1.1 双线程架构与运行原理
1.1.1 什么是小程序
定义:微信小程序是一种不需要下载安装即可使用的应用,用户"扫一扫"或"搜一下"即可打开。小程序基于微信原生能力运行,具有接近原生 App 的体验。
1.1.2 双线程架构
定义:小程序的运行环境分为两个独立的线程——渲染层(Render Thread)和逻辑层(Logic Thread),两者通过 Native 层进行通信。
┌─────────────────────────────────────────┐
│ 微信客户端 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 渲染层 │ │ 逻辑层 │ │
│ │ (WebView) │ │ (JS Core) │ │
│ │ │ │ │ │
│ │ WXML → 节点树 │ │ 数据 + 逻辑 │ │
│ │ WXSS → 样式 │ │ setData() │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ └──── Native 层 ──┘ │
│ (通信桥梁) │
└─────────────────────────────────────────┘各层职责:
| 层级 | 运行环境 | 能力 | 关键技术 |
|---|---|---|---|
| 渲染层 | WebView | 页面渲染、用户交互 | WXML, WXSS, CSS动画 |
| 逻辑层 | JS Core/V8 | 数据处理、API 调用、事件处理 | JavaScript, 框架 API |
| Native 层 | 客户端原生 | 通信桥接、原生组件、硬件能力 | WeixinJSBridge |
通信机制:
逻辑层调用 setData(data)
→ 序列化为 JSON 字符串
→ 通过 Native 层发送到渲染层
→ 渲染层反序列化并更新 DOM 节点
用户点击事件
→ 渲染层捕获事件
→ 序列化事件数据
→ 通过 Native 层发送到逻辑层
→ 逻辑层执行对应的事件处理函数1.1.3 小程序与 H5 的区别
| 特性 | 微信小程序 | 传统 H5 |
|---|---|---|
| 运行环境 | 双线程(WebView + JS Core) | 单线程(浏览器) |
| DOM 操作 | 不支持 | 支持 |
| 离线能力 | 强(缓存 + 本地存储) | 弱(依赖网络) |
| 原生能力 | 强(摄像头、蓝牙、NFC) | 弱(需浏览器支持) |
| 发布方式 | 微信审核 | 直接部署 |
| 开发语言 | WXML + WXSS + JS | HTML + CSS + JS |
1.1.4 小程序项目结构
myapp/
├── app.json # 全局配置(页面路径、窗口样式、TabBar)
├── app.wxss # 全局样式
├── app.js # 全局逻辑(App 实例)
├── project.config.json # 项目配置(开发者工具设置)
├── pages/ # 页面目录
│ ├── index/
│ │ ├── index.wxml # 页面结构
│ │ ├── index.wxss # 页面样式
│ │ ├── index.js # 页面逻辑
│ │ └── index.json # 页面配置
│ └── logs/
│ ├── logs.wxml
│ ├── logs.wxss
│ ├── logs.js
│ └── logs.json
└── utils/ # 工具函数
└── util.js1.2 WXML / WXSS 与数据绑定
1.2.1 WXML 基础语法
定义:WXML(WeiXin Markup Language)是小程序的标记语言,类似 HTML,但使用自定义组件和专属特性。
xml
<!-- pages/index/index.wxml -->
<view class="container">
<!-- 文本 -->
<text class="title">欢迎来到小程序</text>
<!-- 图片 -->
<image src="/images/logo.png" mode="aspectFit" />
<!-- 输入框 -->
<input placeholder="请输入内容" bindinput="onInput" />
<!-- 按钮 -->
<button type="primary" bindtap="onTap">点击我</button>
</view>WXML vs HTML 差异:
| 特性 | WXML | HTML |
|---|---|---|
| 标签名 | view、text、image | div、span、img |
| 数据绑定 | | JS 拼接/DOM 操作 |
| 条件渲染 | wx:if | v-if 或 display |
| 列表渲染 | wx:for | v-for 或 map() |
| 事件绑定 | bindtap / catchtap | onclick |
| 样式单位 | rpx(自适应) | px、rem、vw |
1.2.2 数据绑定
定义:使用 Mustache 语法 将逻辑层的数据绑定到渲染层视图。
xml
<!-- 页面结构 -->
<view class="user-card">
<image src="{{ avatar }}" class="avatar" />
<text class="nickname">{{ nickname }}</text>
<text class="level">等级:{{ level }}</text>
<text class="score">积分:{{ score }} 分</text>
<!-- 属性绑定 -->
<image src="{{ imageUrl }}" mode="{{ imageMode }}" />
<!-- 运算符绑定 -->
<text>{{ score > 100 ? 'VIP用户' : '普通用户' }}</text>
<text>{{ message || '默认消息' }}</text>
</view>javascript
// 页面逻辑
Page({
data: {
nickname: "张三",
avatar: "/images/default-avatar.png",
level: 3,
score: 250,
imageUrl: "https://example.com/banner.jpg",
imageMode: "widthFix",
message: "",
},
});1.2.3 WXSS 与 rpx 单位
定义:WXSS(WeiXin Style Sheets)是小程序的样式语言,在 CSS 基础上扩展了 rpx(responsive pixel)自适应单位。
css
/* app.wxss - 全局样式 */
page {
font-size: 28rpx;
color: #333;
background-color: #f5f5f5;
}
.container {
padding: 30rpx;
}
/* pages/index/index.wxss - 页面样式 */
.title {
font-size: 36rpx;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 20rpx;
}
.user-card {
display: flex;
align-items: center;
padding: 30rpx;
background: #fff;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
margin-right: 20rpx;
}
/* 弹性布局——两列网格 */
.grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.grid-item {
width: 48%;
margin-bottom: 20rpx;
}rpx 换算规则:
rpx = 750 分之一的屏幕宽度
iPhone 6/7/8 (375px): 1px = 2rpx
iPhone 12/13 (390px): 1px ≈ 1.92rpx
iPad: 1px ≈ 1.17rpx1.2.4 常用 WXSS 选择器
css
/* 类选择器 */
.card { }
.card.active { }
/* ID 选择器 */
#header { }
/* 标签选择器 */
view { }
text { }
/* 后代选择器 */
.card .title { }
/* 伪类 */
.button:active { opacity: 0.8; }
/* 常用的重要属性 */
.box {
display: flex; /* 弹性布局 */
justify-content: center; /* 主轴对齐 */
align-items: center; /* 交叉轴对齐 */
position: relative; /* 定位 */
box-sizing: border-box; /* 盒模型 */
}1.3 事件绑定与列表渲染
1.3.1 事件绑定
定义:小程序通过 bind / catch 前缀绑定事件,bind 不阻止冒泡,catch 阻止冒泡。
xml
<view class="container">
<!-- bindtap——点击事件 -->
<button bindtap="handleClick">点击</button>
<!-- 带参数的事件绑定(通过 data-* 传参) -->
<view class="product"
data-id="{{ item.id }}"
data-name="{{ item.name }}"
bindtap="onProductTap">
<text>{{ item.name }}</text>
</view>
<!-- 输入事件 -->
<input bindinput="onInput" bindblur="onBlur" bindfocus="onFocus" />
<!-- 表单提交事件 -->
<form bindsubmit="onSubmit" bindreset="onReset">
<input name="username" />
<button form-type="submit">提交</button>
<button form-type="reset">重置</button>
</form>
<!-- 触摸事件 -->
<view bindtouchstart="onTouchStart"
bindtouchmove="onTouchMove"
bindtouchend="onTouchEnd"
style="width: 100%; height: 300px;">
触摸区域
</view>
<!-- 阻止冒泡(catch) -->
<view bindtap="outerTap">
外层
<view catchtap="innerTap">内层(点击不会冒泡到外层)</view>
</view>
</view>javascript
Page({
// 点击事件
handleClick() {
console.log("按钮被点击");
},
// 获取 data-* 参数
onProductTap(e) {
const { id, name } = e.currentTarget.dataset;
console.log("商品点击:", id, name);
wx.showToast({ title: `点击了 ${name}`, icon: "none" });
},
// 输入事件
onInput(e) {
console.log("输入内容:", e.detail.value);
this.setData({ inputValue: e.detail.value });
},
onBlur(e) {
console.log("输入框失去焦点:", e.detail.value);
},
// 表单提交
onSubmit(e) {
console.log("表单数据:", e.detail.value);
},
// 触摸事件
onTouchStart(e) {
console.log("触摸开始:", e.touches[0].x, e.touches[0].y);
},
onTouchMove(e) {
// 实时触摸位置
},
onTouchEnd(e) {
console.log("触摸结束");
},
});1.3.2 条件渲染
定义:使用 wx:if / wx:elif / wx:else 控制元素的显示与隐藏。
xml
<!-- 条件渲染 -->
<view wx:if="{{ score >= 90 }}" class="grade-a">优秀</view>
<view wx:elif="{{ score >= 60 }}" class="grade-b">及格</view>
<view wx:else class="grade-c">不及格</view>
<!-- 使用 hidden 属性(始终渲染,仅切换 display) -->
<view hidden="{{ !showDetail }}">详情内容</view>
<!-- block 标签——不额外创建 DOM -->
<block wx:if="{{ isLogin }}">
<text>欢迎回来</text>
<button bindtap="logout">退出</button>
</block>
<block wx:else>
<button bindtap="login">登录</button>
</block>wx:if vs hidden 选择:
| 特性 | wx:if | hidden |
|---|---|---|
| 渲染方式 | 条件为 true 时才渲染 | 始终渲染,切换 display |
| 切换开销 | 大(销毁/重建 DOM) | 小(仅切换样式) |
| 适用场景 | 低频切换(如权限判断) | 高频切换(如 Tab) |
1.3.3 列表渲染
定义:使用 wx:for 遍历数组或对象渲染列表,wx:key 提供唯一标识优化性能。
xml
<!-- 基础列表渲染 -->
<view class="list">
<view class="item" wx:for="{{ productList }}" wx:key="id">
<text>{{ index + 1 }}.</text>
<text>{{ item.name }}</text>
<text class="price">¥{{ item.price }}</text>
</view>
</view>
<!-- wx:for-item 和 wx:for-index 自定义变量名 -->
<view wx:for="{{ userList }}"
wx:for-item="user"
wx:for-index="pos"
wx:key="userId">
<text>{{ pos }}. {{ user.nickname }}</text>
</view>
<!-- 嵌套循环 -->
<view wx:for="{{ categories }}" wx:for-item="category" wx:key="id">
<text>{{ category.name }}</text>
<view wx:for="{{ category.items }}" wx:for-item="sub" wx:key="id">
<text> └ {{ sub.name }}</text>
</view>
</view>
<!-- 对象遍历 -->
<view wx:for="{{ object }}" wx:for-item="value" wx:for-index="key">
<text>{{ key }}: {{ value }}</text>
</view>javascript
Page({
data: {
productList: [
{ id: 1, name: "商品A", price: 99.9 },
{ id: 2, name: "商品B", price: 149.9 },
{ id: 3, name: "商品C", price: 299.9 },
],
userList: [
{ userId: "u001", nickname: "张三" },
{ userId: "u002", nickname: "李四" },
],
categories: [
{
id: "c1",
name: "电子产品",
items: [{ id: "s1", name: "手机" }, { id: "s2", name: "电脑" }],
},
{
id: "c2",
name: "服装",
items: [{ id: "s3", name: "上衣" }],
},
],
},
});1.4 页面路由与导航机制
1.4.1 页面栈与路由
定义:小程序用页面栈管理页面导航,每个路由操作都会改变页面栈的结构。
页面栈操作结果:
打开新页面(navigateTo)
[A] → [A, B] → [A, B, C] 栈顶压入
重定向(redirectTo)
[A] → [B] → [C] 替换当前页面
切换 Tab(switchTab)
[A] → [A] 只切换 Tab,不改变栈
返回上一页(navigateBack)
[A, B, C] → [A, B] 弹出栈顶
重启(reLaunch)
[A, B, C] → [D] 清空栈,打开新页面1.4.2 路由 API
javascript
// 1. navigateTo——保留当前页面(可返回)
wx.navigateTo({
url: "/pages/detail/detail?id=1001&name=商品",
events: {
// 从详情页返回时的事件监听
acceptData(data) {
console.log("接收返回数据:", data);
},
},
success(res) {
// 通过 eventChannel 向目标页传递数据
res.eventChannel.emit("sendData", { from: "列表页" });
},
});
// 2. redirectTo——重定向(不可返回)
wx.redirectTo({
url: "/pages/login/login",
});
// 3. switchTab——切换 Tab(路径需在 tabBar 中声明)
wx.switchTab({
url: "/pages/index/index",
});
// 4. navigateBack——返回
wx.navigateBack({
delta: 1, // 返回层数,默认 1
});
// 5. reLaunch——重启(关闭所有页面)
wx.reLaunch({
url: "/pages/index/index",
});1.4.3 页面间传参
javascript
// ===== 方式一:URL 参数(适合少量数据)=====
// 页面 A:跳转时传递
wx.navigateTo({
url: "/pages/detail/detail?id=1001&type=product",
});
// 页面 B:在 onLoad 中接收
Page({
onLoad(options) {
console.log("接收参数:", options);
// { id: "1001", type: "product" }
this.setData({
productId: options.id,
pageType: options.type,
});
},
});
// ===== 方式二:EventChannel(适合返回数据)=====
// 页面 A
wx.navigateTo({
url: "/pages/detail/detail",
events: {
// 监听来自详情页的事件
onBack(data) {
console.log("详情页返回数据:", data);
},
},
});
// 页面 B(详情页)
const eventChannel = this.getOpenerEventChannel();
eventChannel.emit("onBack", { result: "success", selected: true });
// 或 eventChannel.off() 取消监听
// ===== 方式三:全局数据 App.globalData(适合全局共享)=====
// app.js
App({
globalData: {
userInfo: null,
token: "",
},
});
// 任何页面中访问
const app = getApp();
app.globalData.token = "xxx";
console.log(app.globalData.userInfo);1.4.4 页面生命周期
定义:每个页面有一组生命周期函数,在页面创建、显示、隐藏、卸载时自动调用。
javascript
Page({
data: {
pageTitle: "",
loadTime: "",
},
// 页面加载(只调用一次)
onLoad(options) {
console.log("页面加载,参数:", options);
this.setData({ loadTime: new Date().toISOString() });
},
// 页面显示(每次显示都调用)
onShow() {
console.log("页面显示");
// 适合做数据刷新
},
// 页面初次渲染完成(只调用一次)
onReady() {
console.log("页面渲染完成");
},
// 页面隐藏(导航到其他页面时触发)
onHide() {
console.log("页面隐藏");
},
// 页面卸载
onUnload() {
console.log("页面卸载");
// 清理定时器、事件监听等
},
// 下拉刷新
onPullDownRefresh() {
console.log("下拉刷新");
setTimeout(() => {
wx.stopPullDownRefresh(); // 停止刷新动画
}, 1500);
},
// 上拉触底
onReachBottom() {
console.log("触底加载更多");
// 加载下一页数据
},
// 页面滚动
onPageScroll(e) {
console.log("滚动位置:", e.scrollTop);
},
// 分享
onShareAppMessage() {
return {
title: "分享标题",
path: "/pages/index/index",
};
},
});小结
- 双线程架构:渲染层(WebView)+ 逻辑层(JS Core),通过 Native 层通信,不支持直接 DOM 操作
- WXML + WXSS:使用
数据绑定,rpx自适应单位,view/text/image是基础标签 - 事件与渲染:
bind/catch绑定事件,data-*传参;wx:for列表渲染需指定wx:key - 页面路由:
navigateTo(保留)/redirectTo(替换)/switchTab(Tab 切换),onLoad中接收参数
练习
- 创建项目:在微信开发者工具中创建一个新项目,熟悉项目目录结构和配置项。
- 数据绑定:创建一个个人名片页面,使用数据绑定显示姓名、头像、职业和联系方式。
- 列表渲染:从数组中渲染一个商品列表,每个商品显示名称、价格和图片,点击商品显示详情 Toast。
- 页面导航:创建两个页面(首页和详情页),首页通过
navigateTo跳转到详情页并传递商品 ID,详情页在onLoad中接收并显示。 - 生命周期:在页面的
onShow和onHide中添加日志输出,观察页面切换时的生命周期调用顺序。