# 第6次课 讲稿

> 课程：小程序开发
> 主题：事件绑定与页面交互
> 教材：《微信小程序开发实战（第2版）》第3章
> 时长：3学时（理论 1 + 实践 2）
> 重点：事件绑定、事件对象、setData更新视图
> 难点：事件冒泡与捕获、input双向绑定

---

## 一、开场（0:00）

第2章我们把页面画出来了，但那些页面是"死"的——点哪儿都没反应。从今天进入第3章，页面开始"活"过来。

先做个小实验。（在开发者工具里写一个 button，绑上 bindtap，函数里 console.log）大家看，点一下按钮，控制台就打印一行。这就是事件。

今天要建立的核心认知是一个闭环：**用户操作 → 触发事件 → 事件处理函数改数据 → setData → 页面自动更新**。

注意最后一步：你从来不需要手动去改页面。这个闭环搞明白了，交互开发就通了一半。

---

## 二、明确本课目标（约 5:00）

把目标写在白板上或直接翻到课件第 2 页，逐条念一遍：

- 掌握小程序事件绑定的方式与类型
- 理解事件对象的常用属性
- 掌握setData更新视图数据
- 能够实现常见的页面交互效果
- 案例：案例3-1 比较数字大小、案例3-2 计算器

本次课 3 学时的安排是：**理论讲解 1 学时，上机实践 2 学时**——事件机制讲解 1 学时，案例 3-1/3-2 实操 2 学时。

---

## 三、知识点讲解

### 幻灯片 3：事件绑定方式

**【8:00 讲解】**

- bind事件: bindtap / bindinput / bindchange
**catch事件: catchtap**

阻止事件冒泡

- 事件传参: data-* 自定义属性 + e.currentTarget.dataset
- 注意: bindtap无法直接传参, 需用data-*方式
- e.target vs e.currentTarget: target是触发者, currentTarget是绑定者

### 幻灯片 4：常用事件类型

**【13:00 讲解】**

**tap**

点击事件(最常用)

**input**

输入事件(实时触发)

**change**

值改变事件

**submit**

表单提交事件

**scroll**

滚动事件


### 幻灯片 5：setData更新数据

**【18:00 讲解】**

**this.setData({ key: value })**

更新数据并刷新视图

- setData是异步的, 回调函数可确认更新完成
- 只更新需要变化的数据(局部更新)
- 避免频繁调用setData(性能优化)
- setData数据量不宜过大

### 幻灯片 6：案例3-1 比较数字大小 / 案例3-2 计算器

**【23:00 讲解】**

**案例3-1 比较数字大小**

input 收集两个数字，bindtap 触发比较，setData 回显结论

**案例3-2 计算器**

数字键与运算符键共用一个 bindtap，靠 data-key 区分

- 事件传参：data-key 携带按键值，处理函数中用 e.currentTarget.dataset.key 取出
- catchtap 阻止冒泡：防止按键点击穿透到外层容器的点击事件
- 综合运用事件绑定与 setData 完成两个教材案例

---

## 四、难点突破：用类比讲清楚

下面这几个点是学生最容易卡住的地方，抽象讲不通就换成类比。

### bind 与 catch

事件冒泡像水泡从水底往上冒。bindtap 是让水泡继续往上冒，外层容器也会收到；catchtap 是拿盖子把它扣住，到此为止。列表项里放删除按钮时，删除按钮必须用 catchtap，否则点删除会连带触发整行的跳转。

### target 与 currentTarget

target 是"谁被戳中了"，currentTarget 是"谁在负责接待"。你点在按钮里的文字上，target 是那段文字，currentTarget 才是绑了事件的按钮。取 dataset 时用 currentTarget 才稳。

### setData 是异步的

像给食堂窗口递条子：你递完条子，饭不会立刻出现在手上。所以 setData 的下一行代码里读到的可能还是旧值，要拿更新后的结果就得写在 setData 的回调里。

---

## 五、代码演示环节

以下每一段都建议**现场敲**，不要直接贴完整代码。边敲边问"下一行该写什么"，让学生跟上节奏。

### 演示 1：事件绑定方式

**文件：** `event-bind.wxml`　**预期效果：** 事件绑定效果

```html
<!-- bindtap：冒泡事件绑定 -->
<view bindtap="handleTap">
<text>点击我</text>
</view>

<!-- catchtap：阻止事件冒泡 -->
<view bindtap="parentTap">
<view catchtap="childTap">
子元素（阻止冒泡）
</view>
</view>

<!-- data-* 传参 -->
<view bindtap="handleItem" data-id="{{item.id}}" data-name="{{item.name}}">
{{item.name}}
</view>

<!-- e.target vs e.currentTarget -->
// e.target：触发事件的元素（可能是子元素）
// e.currentTarget：绑定事件的元素（始终是当前元素）
handleItem(e) {
const id = e.currentTarget.dataset.id
const name = e.currentTarget.dataset.name
console.log(id, name)
}
```

**讲解口径：**

微信小程序中，**bindtap** 是最常用的事件绑定方式，它允许事件冒泡。如果需要阻止冒泡，使用 **catchtap**。传递参数时，通过 **data-*** 属性携带数据，在事件处理函数中通过 **e.currentTarget.dataset** 获取。注意区分 e.target 和 e.currentTarget：target 是实际触发事件的元素，而 currentTarget 是绑定事件的元素，取参数时务必使用 **e.currentTarget**，避免取到子元素上的数据。

### 演示 2：常用事件类型

**文件：** `events.wxml`　**预期效果：** 常用事件演示

```html
<!-- tap 点击事件 -->
<button bindtap="onTap">点击</button>

<!-- input 输入事件 -->
<input bindinput="onInput" placeholder="请输入" />

<!-- change 改变事件 -->
<switch bindchange="onSwitch" />
<picker bindchange="onPickerChange">
选择城市
</picker>

<!-- submit 提交事件 -->
<form bindsubmit="onSubmit">
<input name="username" />
<button form-type="submit">提交</button>
</form>

<!-- scroll 滚动事件 -->
<scroll-view bindscroll="onScroll" scroll-y>
<view>内容1</view>
<view>内容2</view>
</scroll-view>
```

**讲解口径：**

小程序常用事件有五类：**tap** 是点击事件，最基础最常用；**input** 事件在输入框内容变化时触发，通过 e.detail.value 获取输入值；**change** 事件用于 switch、picker 等组件状态改变时触发；**submit** 事件配合 form 组件，通过 form-type="submit" 触发，可一次性收集所有表单数据；**scroll** 事件在 scroll-view 滚动时触发，可用于实现下拉刷新、滚动加载等效果。

### 演示 3：setData更新数据

**文件：** `setData.js`　**预期效果：** setData 更新效果

```js
Page({
data: {
count: 0,
userInfo: { name: '张三', age: 20 },
list: []
},

// 基本用法：更新单个字段
handleAdd() {
this.setData({
count: this.data.count + 1
})
},

// 更新对象中的某个属性（路径写法）
updateName() {
this.setData({
'userInfo.name': '李四'
})
},

// 更新数组中某一项
updateListItem() {
this.setData({
'list[0]': { id: 1, name: '更新项' }
})
},

// ⚠️ 性能优化：只更新需要变化的数据
// ❌ 不要这样写：整体替换
// this.setData({ list: newList })
// ✅ 应该这样写：只更新变化项
appendList() {
const key = `list[${this.data.list.length}]`
this.setData({ [key]: newItem })
}
})
```

**讲解口径：**

**setData** 是小程序更新视图的唯一方式。基本用法是传入一个对象，键为 data 中的字段名，值为新值。更新嵌套对象时，使用 **路径写法**，如 'userInfo.name'。更新数组某项用 'list[0]'。特别注意性能优化：setData 是**异步操作**，每次调用都会触发视图渲染，所以要避免频繁调用和整体替换大数据，只更新变化的部分。

### 演示 4：案例3-1比较数字大小+案例3-2计算器

**文件：** `compare.wxml`　**预期效果：** 比较数字 & 计算器

```html
<!-- 案例3-1：比较数字大小 compare.wxml -->
<view class="container">
<input bindinput="onInputNum1" type="number"
placeholder="请输入第一个数字" />
<input bindinput="onInputNum2" type="number"
placeholder="请输入第二个数字" />
<button bindtap="compare">比较大小</button>
<view>结果：{{result}}</view>
</view>

// compare.js
Page({
data: { num1: '', num2: '', result: '' },
onInputNum1(e) {
this.setData({ num1: e.detail.value })
},
onInputNum2(e) {
this.setData({ num2: e.detail.value })
},
compare() {
const { num1, num2 } = this.data
if (!num1 || !num2) return
const n1 = Number(num1), n2 = Number(num2)
const res = n1 > n2 ? `${n1}大于${n2}` : n1 `${n1}小于${n2}` : '两数相等'
this.setData({ result: res })
}
})

<!-- 案例3-2：计算器 calc.wxml -->
<view class="calc">
<view class="display">{{display}}</view>
<view class="keys">
<view wx:for="{{keys}}" wx:key="index"
bindtap="onKey" data-val="{{item}}">
{{item}}
</view>
</view>
</view>
```

**讲解口径：**

案例3-1比较数字大小：两个 **input** 通过 bindinput 实时获取输入值存入 data，点击按钮时在 **compare** 函数中比较大小，用 **setData** 更新结果显示。案例3-2计算器：使用 **wx:for** 循环渲染按键，通过 **data-val** 传参，在 onKey 中根据按键类型（数字、运算符、等号、清除）分别处理，核心是字符串拼接和 **eval** 计算表达式。

---

## 六、常见错误与排查

上机环节学生一定会遇到下面这些问题，提前讲一遍能省掉大量重复答疑。

| # | 现象 | 原因与解决 |
|:-:|------|------------|
| 1 | bindtap="handler(1)" 这样传参 | 不支持，会当成函数名的一部分。传参必须用 data-* 属性，处理函数里从 e.currentTarget.dataset 取。 |
| 2 | 一次点击触发了两个函数 | 子元素和父元素都绑了 bindtap，事件冒泡上去了。该阻止的地方用 catch。 |
| 3 | setData 传了整个大数组 | 每次 setData 都会做一次线程间数据传输，传得越多越慢。改列表里第 3 项的名字，正确写法是 setData({ "list[2].name": "x" })，而不是整个 list 重传。 |
| 4 | input 输入框值改不动 | 小程序的 input 不是双向绑定，光设 value 而不在 bindinput 里 setData，用户输入会被"弹回去"。 |

---

## 七、上机实践指引

本次课对应教材案例 2 个，源码已放在站点 `code/session06/` 下：

- **【案例3-1】比较数字大小** — 含 知识储备、案例实现 共 2 个工程
- **【案例3-2】计算器** — 含 知识储备、案例实现 共 2 个工程

建议顺序：先带学生跑一遍「知识储备」里的最小示例，确认每个知识点都能单独跑通，再进「案例实现」做完整案例。直接上完整案例，出错了不知道错在哪。

---

## 八、本课小结

回到课件小结页，逐条回顾：

- 事件绑定方式
- 常用事件类型
- setData更新数据
- 案例3-1比较数字大小+案例3-2计算器

今天两个案例：比较数字大小练的是"取值—计算—回显"这条最基本的链路；计算器练的是"多个按钮共用一个处理函数"这个工程技巧。

挑战任务的科学计算器有点难度，做不完不要紧，但计算器的基础版一定要跑通——它是后面购物车、表单那些交互的地基。

---

## 九、布置作业

三档任务，基础必做，进阶建议做，挑战选做。

### 【基础】比较数字大小

完成案例3-1，实现两个数字的比较功能

### 【进阶】计算器

完成案例3-2，实现简易计算器，将计算逻辑模块化

### 【挑战】科学计算器

在计算器基础上增加括号、百分比、开方等高级运算功能

---

> 配套资源：课件 `slides-v2/session06.pptx`　课堂视频 `videos-output/session06.mp4`　扩展阅读 `docs/第6次课_扩展阅读.md`
