主题
数据绑定、渲染与表单(三)
本页对应 数据绑定、渲染与表单,共 7 段视频,约 1 小时 0 分。
学习目标
- 能给输入框绑定空的
input事件处理函数,并通过控制台验证双向绑定输入警告被屏蔽。 - 能分别使用
data-*与mark两种方式为事件传参,并在事件对象中正确读取参数后通过setData更新页面数据。 - 能基于
activeIndex和当前循环下标动态绑定class,实现“点谁谁高亮”的交互效果。 - 能配置
scroll-view的纵向滚动、滚动触底和下拉刷新属性与事件,完成触底输出日志及下拉刷新动画的开启与关闭。 - 能使用
form、button form-type="submit"、bindsubmit和name属性快速收集表单数据,并正确读取e.detail.value。 - 能在页面生命周期
onLoad或onShow中读取本地存储并渲染数据,能说明二者触发时机差异,并能在onHide中清理定时器。
知识点
4.1 事件对象:先拿事件对象,再谈传参
为什么需要:小程序事件回调不像 Vue 那样可以直接写 @click="fn(123)"。如果照 Vue 的写法写 bind:tap="onChange(123)",小程序会把整段 onChange(123) 当成函数名去解析,结果不仅拿不到参数,连原来的打印都可能失效。因此,模板数据要传到回调里,必须借助事件对象。
怎么想:事件回调的第 1 个参数就是事件对象。先绑定事件确认能触发,再观察事件对象里有什么;传参时优先用 mark,老写法用 data-*。
javascript
Page({
eventHandler(ev) {
// 查看事件对象
console.log(ev)
}
})怎么做:老写法在标签上写 data-index="{{index}}",回调里通过 ev.target.dataset.index 取;新版推荐写 mark:index="{{index}}",回调里通过 ev.mark.index 取,层级更少。传多个参数时,mark 可写成对象,再解构使用。
易错点:dataset 层级深、没有提示,容易拼错;mark 相对简洁,但要注意基础库兼容。转写稿里的调试过程是:先绑 bind:tap 确认事件通,再试 onChange(123) 发现打印都没了,改 data-index 后能看到 ev.target.dataset.index,最后改 mark 看到 ev.mark.index。
4.1.1 点击高亮:把下标存起来再动态绑类名
为什么需要:点谁谁高亮是常见交互,核心是把“当前点击的下标”存到 data 中,再和循环下标比较。
怎么想:准备 activeIndex,默认 0;点击时用事件对象拿到 index,setData 更新;模板里用三元表达式决定是否加 active。
怎么做:class 在小程序里不要写两个,统一写在一个 class 里挖空:class="item {{activeIndex === index ? 'active' : ''}}"。循环可用 wx:for="{{6}}" 快速生成 6 个盒子,index 从 0 开始,wx:key="*this"。
易错点:不要写两个 class,否则会覆盖;比较时确认 activeIndex 与当前 index 类型一致;改完先看 setData 后 activeIndex 是否真的更新。
本节练习结构:
xml
<!-- 搜索框 -->
<view class="search-bar">
<input type="text" placeholder="输入搜索关键字" />
</view>
<!-- 页面主体 -->
<view class="page-body">
<scroll-view scroll-y class="aside">
<view wx:for="{{6}}" wx:key="*this" class="item"></view>
</scroll-view>
<scroll-view scroll-y refresher-enabled class="content">
<view wx:for="{{6}}" wx:key="*this" class="item"></view>
</scroll-view>
</view>4.2 组件事件:滚动容器与表单
为什么需要:tap 是通用事件,但 scroll-view 的触底、下拉刷新,以及表单的 change、submit 只属于特定组件,必须用组件事件。
怎么想:先查文档确认事件名和开启属性;滚动容器先解决“能不能滚”,再解决“触底/下拉后做什么”。
怎么做:scroll-view 竖向滚动要加 scroll-y,并且必须有高度;触底用 bind:scrolltolower,常用于加载分页数据;下拉刷新先加 refresher-enabled 开启,再绑 bind:refresherrefresh,下拉时把 isPulling 置 true,请求完成在 complete 里置 false,并用下拉刷新状态属性绑定它来收起动画。
javascript
Page({
data: {
isPulling: false
},
onScrollPulling() {
// 请求最新的数据
wx.request({
url: 'https://hmajax.itheima.net/api/books',
data: {creator: 'zhangsan'},
success: (result) => {},
complete: () => {
// 停止下拉刷新
this.setData({isPulling: false})
}
})
}
})易错点:没有高度滚不起来;没开 refresher-enabled 拉不动;下拉触发后动画不隐藏,要更新状态标记;事件名建议写 bind:,分隔更明显。转写稿调试时先改标签没变化,查文档补高度和 scroll-y 后才能滚;下拉时先开启再绑定,触发后还要在 complete 中停止。
表单提交用于批量收集数据:
xml
<!-- 用户信息 -->
<view class="register">
<form>
<view class="form-field">
<label for="">姓名:</label>
<view class="field">
<input type="text" placeholder="请输入您的姓名" />
</view>
</view>
<view class="form-field">
<label for="">性别:</label>
<view class="field">
<radio-group>
<radio value="男" checked />男
<radio value="女" />女
</radio-group>
</view>
</view>
<view class="form-field">
<label for="">爱好:</label>
<view class="field">
<checkbox-group>
<checkbox value="写代码" checked />写代码
<checkbox value="睡大觉" />睡大觉
</checkbox-group>
</view>
</view>
<view class="form-field">
<label for="">籍贯:</label>
<view class="field">
<picker mode="region">请选择籍贯</picker>
</view>
</view>
<button size="mini" type="primary">保存</button>
</form>
</view>怎么做:给 form 绑 bindsubmit,按钮加 form-type="submit";给 input、radio-group、checkbox-group、picker 加 name,提交时从 ev.detail.value 一次拿到数据。change 用于表单数据改变(笔记说明 input 不支持),适合需要校验、回显的复杂表单。
易错点:按钮没写 form-type="submit" 不触发提交;控件没写 name,value 里就是空;picker 快速收集可能不自动展示,且可能拿到编码而非中文,复杂业务仍建议用 change + 数据绑定。转写稿调试时先点提交能输出但 value 为空,补 name 后才收集到姓名、性别、爱好、城市。补充:控制台双向绑定输入警告可选用空 input 事件覆盖,不是业务必需。
生命周期是什么
为什么需要:页面和小程序都有“什么时候加载、什么时候显示、什么时候隐藏”的时机。生命周期就是名称固定、到时机自动执行的函数,用来把请求、更新、清理放到正确的位置。
怎么想:先分清“只执行一次”和“每次可见都执行”。页面级看 onLoad / onShow / onReady / onHide,应用级看 onLaunch / onShow / onHide。
怎么做:在 Page({}) 或 App({}) 中按固定名称声明函数。它们由框架自动调用,不需要手动执行。
易错点:onLoad 只执行 1 次,不等于每次回到页面都执行;onShow 每次页面可见都会执行;onHide 是隐藏,不是销毁。
页面生命周期:onLoad 与 onShow
为什么需要:onLoad 适合获取地址参数和发网络请求;onShow 适合动态更新数据或状态。转写稿里的图书案例说明:查询书单只需首次加载时读本地存储,用 onLoad 就够;购物车这类每次打开都要最新数据,才更适合 onShow。
怎么想:先问“这个逻辑只做一次,还是每次看见页面都要做”。只做一次放 onLoad,每次可见都做放 onShow。
怎么做:本节练习结构如下:
xml
<view class="history">
<view class="title">历史搜索</view>
<view class="keywords">
<navigator
wx:for="{{history}}"
wx:key="*this"
url="/pages/test/index">
{{item}}
</navigator>
</view>
</view>
<view class="form-field">
<input type="text" placeholder="请输入手机号" />
<text
wx:if="{{time === 0}}"
bind:tap="getSMSCode"
class="label"
>获取短信验证码</text>
<text wx:else class="label">{{time}}后重新获取</text>
</view>转写稿中的本地存储调试可归纳为:查询后 wx.setStorageSync('list', res.data.data),页面加载时 wx.getStorageSync('list') 再 setData。这里要注意同音字纠正:是 setData、setStorageSync、getStorageSync,不是“set date / set storage aging”。
易错点:不要把只需要首次执行的请求放进 onShow,否则每次切回页面都请求;也不要把必须每次刷新的数据只放 onLoad。
页面生命周期:onReady 与 onHide
为什么需要:onReady 在页面初次渲染完成时执行,只执行 1 次,适合节点操作或动画交互;onHide 在页面不可见时执行,适合销毁长时间运行的任务,如定时器。
怎么想:onReady 关注“渲染完成”,onHide 关注“页面藏起来了”。转写稿调试发现:切走页面时页面通常没有销毁,只是隐藏,所以定时器还会继续跑,需要在 onHide 清理。
怎么做:倒计时示例保留如下:
javascript
// 定时器ID
let timer = null
Page({
data: {
history: [],
time: 0
},
getSMSCode() {
let time = 60 // 初始数据
// 倒计时
timer = setInterval(() => {
// 停止定时器
if(--time < 0) return clearInterval(timer)
this.setData({time}) // 渲染结果
}, 1000)
},
})实际调试时,可在 onHide 中 clearInterval(timer)。转写稿强调要把定时器 ID 存起来,隐藏时才能清理。
易错点:onReady 只执行 1 次,不能代替 onShow;onHide 不是页面销毁;清理定时器前必须能拿到定时器 ID。
调试触发时机与选择原则
为什么需要:生命周期名称相近,单看定义容易混。转写稿通过打印验证:刷新页面时 onLoad 触发 1 次;切走再回来,onLoad 不再触发;每次可见会触发 onShow;切走触发 onHide。
怎么想:用“首次加载、每次显示、初次渲染、隐藏清理”四个关键词区分。图书本地存储案例中,onLoad 和 onShow 都能读出数据,但业务只需首次读取时优先 onLoad;若逻辑复杂且每次显示都要更新,再考虑 onShow。
怎么做:在生命周期里加 console.log 观察顺序,再决定请求和清理放在哪里。
易错点:不要因为“都能跑”就随意选;要按业务频率选择,避免重复请求和性能浪费。
应用生命周期
为什么需要:应用生命周期控制小程序整体启动、前台运行和后台运行。onLaunch 在小程序启动时执行 1 次,常用于获取场景值或启动参数;onShow 在小程序前台运行时执行,常用于更新数据或状态;onHide 在小程序后台运行时执行,常用于销毁长时间运行的任务,如定时器。
怎么想:页面生命周期管某个页面,应用生命周期管整个小程序;同名的 onShow / onHide 要注意作用域不同。
怎么做:本节练习代码演示自定义分享:
javascript
// pages/lifetimes/index.js
Page({
// 小程序转发/分享
onShareAppMessage() {
return {
title: '小程序学习',
path: '/pages/index/index?test=测试数据',
imageUrl: '/static/images/cover.png'
}
}
})易错点:onLaunch 只执行 1 次,不要放每次前台都要刷新的逻辑;应用 onHide 适合清理全局任务,页面 onHide 适合清理页面任务,二者不要混用。
课件图示






代码示例
示例一:事件对象传参 + 点谁谁高亮
xml
<!-- pages/event/index.wxml -->
<view class="page-body">
<!-- 左侧:列表,点击哪一项就高亮哪一项 -->
<scroll-view scroll-y class="aside">
<view
wx:for="{{6}}"
wx:key="*this"
class="item {{activeIndex === index ? 'active' : ''}}"
mark:index="{{index}}"
bind:tap="onChange"
></view>
</scroll-view>
<!-- 右侧:内容区 -->
<scroll-view scroll-y class="content">
<view wx:for="{{6}}" wx:key="*this" class="item"></view>
</scroll-view>
</view>javascript
// pages/event/index.js
Page({
data: {
activeIndex: 0 // 当前高亮的下标,默认高亮第一个
},
// 事件回调函数的第 1 个参数就是事件对象
onChange(ev) {
console.log(ev) // 可看到 ev.mark、ev.currentTarget.dataset
this.setData({
// 新写法:mark:index → ev.mark.index(少一层)
activeIndex: ev.mark.index
// 老写法:data-index="{{index}}" → ev.currentTarget.dataset.index
})
}
})css
/* pages/event/index.wxss */
.page-body { display: flex; height: 100vh; }
.aside { width: 200rpx; height: 100%; background: #f5f5f5; }
.content { flex: 1; height: 100%; }
.item { height: 100rpx; margin: 20rpx; background: #e6e6e6; border-radius: 8rpx; }
/* 高亮样式:对应 .item 上的 active 类名 */
.aside .item.active { background: pink; }关键行与易错点
- 事件回调不能像 Vue 那样写小括号传参:
bind:tap="onChange(1)"会被小程序把整串onChange(1)当作函数名解析,结果连打印都没有。传参必须借助事件对象。 - 传参两种写法:
data-index="{{index}}"→ev.currentTarget.dataset.index(多一层、易拼错);推荐mark:index="{{index}}"→ev.mark.index。 - 动态类名只能写一个
class,写两个class会互相覆盖;在同一个class里用{{ 条件 ? 'active' : '' }}三元表达式。 - 高亮条件:
activeIndex === index,index是wx:for自带的循环下标。 wx:for="{{6}}"可直接生成 6 项(item 与 index 都是 0~5),wx:key="*this"消除警告。
示例二:scroll-view 滚动触底 + 下拉刷新
xml
<!-- pages/scroll/index.wxml -->
<scroll-view
scroll-y
class="content"
refresher-enabled
refresher-triggered="{{isPulling}}"
bind:scrolltolower="onScrollToLower"
bind:refresherrefresh="onScrollPulling"
>
<view wx:for="{{list}}" wx:key="*this" class="item">{{item}}</view>
</scroll-view>javascript
// pages/scroll/index.js
Page({
data: {
isPulling: false, // 控制下拉刷新动画是否保持
list: [1, 2, 3, 4, 5, 6]
},
// 滚动到底部(或最右侧)时触发,常用于加载分页数据
onScrollToLower() {
console.log('滚动触底')
const list = this.data.list
this.setData({ list: [...list, list.length + 1] })
},
// 下拉刷新触发
onScrollPulling() {
this.setData({ isPulling: true }) // 先标记 true,动画才会保持
wx.request({
url: 'https://hmajax.itheima.net/api/books',
data: { creator: 'zhangsan' },
success: (result) => {
console.log(result)
},
complete: () => {
// 请求结束(成功或失败)再收起动画
this.setData({ isPulling: false })
}
})
}
})css
/* pages/scroll/index.wxss */
.content { height: 600rpx; }
.item { height: 200rpx; margin: 20rpx; background: #e6e6e6; line-height: 200rpx; text-align: center; }关键行与易错点
scroll-view想滚动必须同时满足:有高度 + 指定滚动方向(scroll-y竖向 /scroll-x横向),少一个都滚不动。高度常由父容器 flex 撑开。- 下拉刷新必须先用
refresher-enabled开启,不开启连拉都拉不下来,bind:refresherrefresh也就永远不触发。 - 动画收不起来是高频问题:要用
refresher-triggered="{{isPulling}}"绑定一个动态值,下拉时置true,请求complete里再置false。 complete里用箭头函数,this才指向页面实例;写成function(){}会导致this.setData报错。bind:的冒号写不写都支持,但建议写上,把“绑定关键字”和“事件类型”区分清楚。
示例三:表单 submit 快速收集 + 页面生命周期
xml
<!-- pages/form/index.wxml -->
<view class="register">
<form bind:submit="onSubmit">
<view class="form-field">
<label>姓名:</label>
<view class="field">
<input type="text" name="username" placeholder="请输入您的姓名" />
</view>
</view>
<view class="form-field">
<label>性别:</label>
<view class="field">
<radio-group name="gender">
<radio value="男" checked />男
<radio value="女" />女
</radio-group>
</view>
</view>
<view class="form-field">
<label>爱好:</label>
<view class="field">
<checkbox-group name="hobby">
<checkbox value="写代码" checked />写代码
<checkbox value="睡大觉" />睡大觉
</checkbox-group>
</view>
</view>
<view class="form-field">
<label>籍贯:</label>
<view class="field">
<picker mode="region" name="city">请选择籍贯</picker>
</view>
</view>
<!-- 必须写 form-type="submit",submit 事件才会触发 -->
<button size="mini" type="primary" form-type="submit">保存</button>
</form>
<view class="form-field">
<input type="text" placeholder="请输入手机号" />
<text wx:if="{{time === 0}}" bind:tap="getSMSCode" class="label">获取短信验证码</text>
<text wx:else class="label">{{time}}后重新获取</text>
</view>
</view>javascript
// pages/form/index.js
let timer = null // 定时器 ID 放在模块作用域,方便清理
Page({
data: {
time: 0
},
// 页面加载完成:只执行 1 次,常用于读缓存 / 发请求
onLoad() {
const form = wx.getStorageSync('form') || {}
console.log('onLoad 只执行一次,缓存表单:', form)
},
// 页面显示:每次可见都执行,常用于动态更新数据
onShow() {
console.log('onShow 页面显示')
},
// 页面初次渲染完成:只执行 1 次,常用于节点操作或动画
onReady() {
console.log('onReady 初次渲染完成')
},
// 页面隐藏:常用于销毁长时间运行的任务
onHide() {
clearInterval(timer)
console.log('onHide 页面隐藏,定时器已清理')
},
// 表单提交:ev.detail.value 一次性收集所有带 name 的表单项
onSubmit(ev) {
console.log(ev.detail.value) // { username, gender, hobby, city }
wx.setStorageSync('form', ev.detail.value)
},
// 倒计时
getSMSCode() {
let time = 60
timer = setInterval(() => {
if (--time < 0) return clearInterval(timer)
this.setData({ time })
}, 1000)
}
})关键行与易错点
button不加form-type="submit",bind:submit不会触发;每个要收集的组件都要写name,否则ev.detail.value里就没有它。- 快速收集适合简单表单;
input没有change事件(用双向绑定),picker收集到的是中文而非编码、也不会回显,复杂校验场景仍要用bind:change方式收集。 onLoad只执行 1 次(如首页请求、读缓存),onShow每次可见都执行(如购物车每次切回都要最新数据)——选错会把请求重复发很多次。- 定时器 ID 必须存起来(模块变量或挂在
this上),否则onHide里无法clearInterval;页面隐藏时定时器仍在跑,会持续消耗性能。 - 生命周期函数名固定、自动执行,写成
onload(小写)不会被调用。
常见错误
1. 事件绑定时加了小括号,回调完全不执行
- 现象:写成
bind:tap="changeTab(1)"后点击无任何反应,控制台也不报错,之前的console.log也不再输出。 - 原因:小程序把引号内整个字符串当成函数名解析,即去找名为
changeTab(1)的方法,自然找不到。 - 修法:只写函数名
bind:tap="changeTab",参数改用data-index="1"或mark:index="1"传递,在回调里通过事件对象取。
2. 取参时 target 与 currentTarget 混用,拿到的 dataset 是空或错的
- 现象:循环渲染的列表项里包了子元素(如
<text>),点文字时ev.target.dataset.index为undefined,点空白处又正常。 - 原因:
target是真正被点中的那个节点,currentTarget才是绑定事件的那个节点。 - 修法:统一用
ev.currentTarget.dataset.xxx;用mark时直接ev.mark.xxx(注意 mark 不在 target 下)。
3. scroll-view 加了 scroll-y 却滚不动
- 现象:内容明显溢出,但容器没有滚动条、手指滑不动。
- 原因:滚动容器没有确定高度,或只写了
scroll-y没写布尔值/没给父容器高度约束。 - 修法:给 scroll-view(或其父容器)用 wxss 设死高度(如
flex:1配合父级固定高度),并确认scroll-y已开启。
4. 绑了 bindrefresherrefresh 但下拉没反应
- 现象:事件回调根本不触发,页面拉不下来。
- 原因:没有先开启
refresher-enabled,事件虽然绑了但容器不具备下拉能力。 - 修法:先加
refresher-enabled,再绑bind:refresherrefresh。
5. 下拉刷新转圈动画不消失
- 现象:回调执行完,loading 圈一直挂在那儿。
- 原因:只处理了业务逻辑,没有把
refresher-triggered绑定的状态量改回false。 - 修法:用
refresher-triggered="{{isPulling}}"动态绑定,下拉时置true,请求complete里setData({isPulling:false})。
6. 表单 submit 收集到的 value 是空对象
- 现象:
ev.detail.value打印出来是{}或只有部分字段。 - 原因:按钮没写
form-type="submit",或内部控件漏写name属性。 - 修法:按钮补
form-type="submit",每个要收集的 input/radio-group/checkbox-group/picker 都补name。
7. 在 onShow 里做只该做一次的事
- 现象:切走再切回页面,重复发请求、数据被覆盖、列表闪烁。
- 原因:把首屏初始化逻辑写进了
onShow,而它每次页面可见都会执行。 - 修法:一次性初始化放
onLoad(或onReady),只有"必须实时刷新"的数据(如购物车数量)才留在onShow。
8. 定时器在页面隐藏后仍在跑,且清不掉
- 现象:切到别的页面,控制台还在每秒打印;
clearInterval无效。 - 原因:定时器 ID 存在局部变量或
data里,页面隐藏后拿不到;data里的值还会被setData序列化,不适合存 ID。 - 修法:把 ID 挂在
this上(this.timer = setInterval(...)),在onHide里clearInterval(this.timer),需要时在onShow重新开启。
练习
练习 1|传参方式对比 用 wx:for="{{5}}" 渲染 5 个盒子,分别用 data-index 和 mark:index 两种方式传下标,在同一个回调里同时打印 ev.currentTarget.dataset.index 与 ev.mark.index。要求:把两行打印结果贴出来,说明为什么 mark 少一层访问,并指出哪种写法在你后续点谁谁高亮的需求里更省事。
练习 2|点谁谁高亮(完整版) 基于练习 1 的结构,实现"点谁谁高亮":默认高亮第 0 项,点击任意项后高亮切换。要求:
- 只写一个
class,用三元表达式动态拼接active; - 在注释里写清楚"高亮下标"和"当前循环下标"是怎么比较的;
- 额外加一个"重置"按钮,把高亮恢复为第 0 项。
练习 3|滚动容器双事件 搭建左右两个 scroll-view(左窄右宽),各放 20 个 item:
- 左侧开启竖向滚动 + 触底事件,触底时向数组追加 5 条数据(模拟分页);
- 右侧开启下拉刷新,下拉后延时 1 秒"加载新数据"并自动收起 loading 动画。 要求:注释说明下拉刷新为什么必须两步(开启 + 状态回置),并截图对比"只开启不绑定"与"完整配置"的差别。
练习 4|表单两种收集路线 复制课程里的注册表单(姓名/性别/爱好/籍贯),分别用两种方式收集数据:
- 路线 A:
form+bind:submit+ 每个控件name; - 路线 B:
bind:change/ 双向绑定逐项收集。 要求:写一段 100 字以内的对比结论,说明如果籍贯 picker 最终要提交的是"行政区划编码"而不是中文,哪条路线更合适,为什么。
练习 5|生命周期排雷 写一个页面:onLoad 里发一次模拟请求并打印、onShow 里打印"可见"、onHide 里清定时器;同时在页面里放一个每秒自增的计数器定时器。操作步骤:进入页面 → 切到别的页面 → 切回 → 再切走。要求:
- 记录每次打印的顺序,标注哪些执行了多次、哪些只有一次;
- 回答:如果把"从本地存储读取历史搜索词"这段逻辑放在
onShow,会出现什么可观察到的副作用?如何用最少的改动验证你的判断?
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 补充-屏蔽双向绑定输入警告 | 02:06 |
| 2 | 事件对象-事件传参 | 11:27 |
| 3 | 事件对象-点击高亮效果 | 05:10 |
| 4 | 滚动容器-滚动触底和下拉刷新 | 18:18 |
| 5 | 表单另一种方式收集数据 | 07:06 |
| 6 | 页面生命周期 | 11:36 |
| 7 | 页面生命周期-图书本地存储 | 04:35 |
小结
- 传参的本质是"借用节点属性":小程序没有小括号传参,
dataset和mark都是在节点上挂数据、再从事件对象捞回来;mark访问层级少、书写更省心,新代码优先用它,读老项目要认得dataset。 - 事件对象是唯一入口:无论取参、取表单值(
ev.detail.value)还是取滚动状态,回调的第一个参数都是信息源,写回调前先console.log一遍事件对象,比猜属性名快得多。 - 组件事件有前置条件:
scroll-view的下拉刷新、表单的 submit 都不是"绑了就生效",而是"先配属性、再绑事件、最后回置状态"三步走,缺任一步表现都是"没反应"。 - 动态类名只能有一个 class:小程序不像 Vue 能写两个
class,所有条件类名统一收进一个花括号表达式里拼三元,这是写高亮、选中态的唯一姿势。 - 生命周期选择的判据是"执行次数语义":
onLoad/onReady只跑一次,用于初始化和一次性拉取;onShow每次可见都跑,用于必须实时的数据;onHide是清理资源的唯一可靠时机。选错的代价是重复请求或数据不新鲜。 - 定时器必须成对管理:开的地方和关的地方要能互相拿到 ID,所以 ID 挂
this而不是data;onHide不清,页面隐藏后仍在耗性能且切回来无法恢复。 - 表单收集要按业务复杂度选路线:
submit + name快但弱,无绑定、无校验、picker 中文/编码转换会卡住;一旦涉及校验、回显或编码转换,回到逐项绑定才是正解。 - 控制台噪音可以按需屏蔽:双向绑定的输入警告不影响业务,需要时补一个空的
input事件回调即可覆盖;这属于可选项,别把它当成必修知识点。