Skip to content

数据绑定、渲染与表单(三) ​

本页对应 数据绑定、渲染与表单,共 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 适合清理页面任务,二者不要混用。

课件图示 ​

课件第20页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第21页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第22页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第10页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

课件第19页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 本

课件第23页:多⼀句没有,少⼀句不⾏,用更短时间,教会更实用的技术! ⾼级软件⼈才培训专家 l

代码示例 ​

示例一:事件对象传参 + 点谁谁高亮 ​

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="&#123;&#123;index&#125;&#125;" → ev.currentTarget.dataset.index(多一层、易拼错);推荐 mark:index="&#123;&#123;index&#125;&#125;" → ev.mark.index。
  • 动态类名只能写一个 class,写两个 class 会互相覆盖;在同一个 class 里用 &#123;&#123; 条件 ? 'active' : '' &#125;&#125; 三元表达式。
  • 高亮条件:activeIndex === index,index 是 wx:for 自带的循环下标。
  • wx:for="&#123;&#123;6&#125;&#125;" 可直接生成 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="&#123;&#123;isPulling&#125;&#125;" 绑定一个动态值,下拉时置 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="&#123;&#123;isPulling&#125;&#125;" 动态绑定,下拉时置 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="&#123;&#123;5&#125;&#125;" 渲染 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

小结 ​

  1. 传参的本质是"借用节点属性":小程序没有小括号传参,dataset 和 mark 都是在节点上挂数据、再从事件对象捞回来;mark 访问层级少、书写更省心,新代码优先用它,读老项目要认得 dataset。
  2. 事件对象是唯一入口:无论取参、取表单值(ev.detail.value)还是取滚动状态,回调的第一个参数都是信息源,写回调前先 console.log 一遍事件对象,比猜属性名快得多。
  3. 组件事件有前置条件:scroll-view 的下拉刷新、表单的 submit 都不是"绑了就生效",而是"先配属性、再绑事件、最后回置状态"三步走,缺任一步表现都是"没反应"。
  4. 动态类名只能有一个 class:小程序不像 Vue 能写两个 class,所有条件类名统一收进一个花括号表达式里拼三元,这是写高亮、选中态的唯一姿势。
  5. 生命周期选择的判据是"执行次数语义":onLoad/onReady 只跑一次,用于初始化和一次性拉取;onShow 每次可见都跑,用于必须实时的数据;onHide 是清理资源的唯一可靠时机。选错的代价是重复请求或数据不新鲜。
  6. 定时器必须成对管理:开的地方和关的地方要能互相拿到 ID,所以 ID 挂 this 而不是 data;onHide 不清,页面隐藏后仍在耗性能且切回来无法恢复。
  7. 表单收集要按业务复杂度选路线:submit + name 快但弱,无绑定、无校验、picker 中文/编码转换会卡住;一旦涉及校验、回显或编码转换,回到逐项绑定才是正解。
  8. 控制台噪音可以按需屏蔽:双向绑定的输入警告不影响业务,需要时补一个空的 input 事件回调即可覆盖;这属于可选项,别把它当成必修知识点。