Skip to content

章节 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 + JSHTML + 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.js

1.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 差异

特性WXMLHTML
标签名viewtextimagedivspanimg
数据绑定JS 拼接/DOM 操作
条件渲染wx:ifv-ifdisplay
列表渲染wx:forv-formap()
事件绑定bindtap / catchtaponclick
样式单位rpx(自适应)pxremvw

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.17rpx

1.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:ifhidden
渲染方式条件为 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",
    };
  },
});

小结

  1. 双线程架构:渲染层(WebView)+ 逻辑层(JS Core),通过 Native 层通信,不支持直接 DOM 操作
  2. WXML + WXSS:使用 数据绑定,rpx 自适应单位,view/text/image 是基础标签
  3. 事件与渲染bind / catch 绑定事件,data-* 传参;wx:for 列表渲染需指定 wx:key
  4. 页面路由navigateTo(保留)/ redirectTo(替换)/ switchTab(Tab 切换),onLoad 中接收参数

练习

  1. 创建项目:在微信开发者工具中创建一个新项目,熟悉项目目录结构和配置项。
  2. 数据绑定:创建一个个人名片页面,使用数据绑定显示姓名、头像、职业和联系方式。
  3. 列表渲染:从数组中渲染一个商品列表,每个商品显示名称、价格和图片,点击商品显示详情 Toast。
  4. 页面导航:创建两个页面(首页和详情页),首页通过 navigateTo 跳转到详情页并传递商品 ID,详情页在 onLoad 中接收并显示。
  5. 生命周期:在页面的 onShowonHide 中添加日志输出,观察页面切换时的生命周期调用顺序。

Python 学习资料