Skip to content

微信小程序_2


四、事件处理


  • 一个应用仅仅只有界面展示是不够的,还需要和用户做交互,

    例如:响应用户的点击、获取用户输入的值等等,在小程序里边,

    我们就通过编写 JS 脚本文件来处理用户的操作


4.1 事件绑定&事件对象


  • 小程序中绑定事件与在网页开发中绑定事件几乎一致,只不过在小程序不能通过 on 的方式绑定事件,

    也没有 click 等事件,小程序中绑定事件使用 bind 方法,click 事件也需要使用 tap 事件来进行代替,

    绑定事件的方式有两种:


  • 第一种方式:bind:事件名bind 后面需要跟上冒号,冒号后面跟上事件名
html
<button bind:tap="handler">按钮</button>

  • 第二种方式:bind事件名bind 后面直接跟上事件名
html
<button bindtap="handler">按钮</button>

  • 事件处理函数需要写到 .js 文件中,在 .js 文件中需要调用小程序提供的 Page 方法

    来注册小程序的页面,我们可以直接在 Page 方法中创建事件处理函数。例如:

js
// pages/home/home.js

Page({
  // 页面的初始数据
  data: {},

  // 事件处理程序
  handler () {
    console.log('我被执行啦~~~')
  }
    
  // 其它 code....
})

  • 当组件触发事件时,绑定的事件的处理函数会收到一个事件对象,用来记录事件发生时的相关信息。

    在触发事件时,事件处理程序会主动的给我们传入一个参数 —— event(事件对象)

js
// pages/home/home.js
Page({
  // 页面的初始数据
  data: {},

  // 事件处理程序
  handler (event) {
    // console.log('我被触发了~~~')
    console.log(event)
  }
    
  // 其他 code...
})

4.2 绑定&组织事件冒泡


  • 事件分为冒泡事件和非冒泡事件:

    • 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。

    • 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。


  • 使用 bind 绑定的事件,会触发事件冒泡,如果想阻止事件冒泡,可以使用 catch 来绑定事件。

html
<view bindtap="parentHandler">
  <!-- 使用 bind 绑定的事件,会产生事件冒泡 -->
  <!-- <button bindtap="handler">按钮</button> -->

  <!-- 使用 catcht 绑定的事件,会阻止事件冒泡 -->
  <button catchtap="handler">按钮</button>
</view>
js
Page({
  // 页面的初始数据
  data: {},

  // 事件处理程序
  handler (event) {
    console.log('我是子绑定的事件 ~~~')
  },
  
  parentHandler () {
    console.log('我是父绑定的事件 ~~~')
  }
    
  // 其他 code...
})

WXML 中冒泡事件列表如下表:

类型触发条件
touchstart手指触摸动作开始
touchmove手指触摸后移动
touchcancel手指触摸动作被打断,如来电提醒,弹窗
touchend手指触摸动作结束
tap手指触摸后马上离开(点击)
longpress手指触摸后,超过 350ms 再离开(长按),
如果指定了事件回调函数并触发了这个事件,tap 事件将不被触发
longtap手指触摸后,超过 350ms 再离开(推荐使用 longpress 事件代替)
transitionend会在 WXSS transitionwx.createAnimation 动画结束后触发
animationstart会在一个 WXSS animation 动画开始时触发
animationiteration会在一个 WXSS animation 一次迭代结束时触发
animationend会在一个 WXSS animation 动画完成时触发
touchforcechange在支持 3D TouchiPhone 设备,重按时会触发

📌 注意事项

  • 除上表之外的其他组件自定义事件,如无特殊声明都是非冒泡事件

    例如 formsubmit 事件,inputinput 事件


4.3 事件传参


4.3.1 data-* 自定义数据


  • 在小程序中,可以通过事件传参的方式,将数据传递给事件处理函数。

    常见的事件包括点击事件、输入事件等。

  • 在组件节点中可以通过 data- 的方式传递一些自定义数据,

    传递的数据可以通过事件对象的方式进行获取


📌 注意事项

  • 使用 data- 方法传递参数的时候,多个单词由连字符 - 连接

  • 连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符

  • 例如:

    • data-element-type ,最终会呈现为 event.currentTarget.dataset.elementType

    • data-elementType ,最终会呈现为 event.currentTarget.dataset.elementtype


事件传参
  • wxml 文件中,使用 data-* 属性将数据传递给事件处理函数。例如:
html
<view bindtap="parentHandler" data-parent-id="1" data-parentName="tom">
  <!-- 如果需要进行事件传参,需要再组件上通过 data- 的方式传递数据 -->
  <!-- <button bindtap="btnHandler" data-id="1" data-name="tom">按钮</button> -->

  <button data-id="1" data-name="tom">按钮</button>
</view>

  • js 文件中,可以通过 event.currentTarget.dataset 获取传递的数据
js
// cate.js
Page({

  // 按钮触发的事件处理函数
  btnHandler (event) {
    // currentTarget 事件绑定者,也就是指:哪个组件绑定了当前事件处理函数
    // target 事件触发者,也就是指:哪个组件触发了当前事件处理函数
    // currentTarget 和 target 都是指按钮,因为是按钮绑定的事件处理函数,同时点击按钮触发事件处理函数
    // 这时候通过谁来获取数据都可以
    console.log(event.currentTarget.dataset.id)
    console.log(event.target.dataset.name)
  },


  // view 绑定的事件处理函数
  parentHandler (event) {
    // 点击蓝色区域(不点击按钮)
    // currentTarget 事件绑定者:view
    // target 事件触发者:view
    // currentTarget 和 target 都是指 view,如果想获取 view 身上的数据,使用谁都可以

    // 点击按钮(不点击蓝色区域)
    // currentTarget 事件绑定者:view
    // target 事件触发者:按钮
    // 如果想获取 view 身上的数据,就必须使用 currentTarget 才可以
    // 如果想获取的是事件触发者本身的数据,就需要使用 target
    console.log(event)

    // 在传递参数的时候,如果自定义属性是多个单词,单词与单词直接使用中划线 - 进行连接
    // 在事件对象中会被转换为小托峰写法
    console.log(event.currentTarget.dataset.parentId)

    // 在传递参数的时候,如果自定义属性是多个单词,单词如果使用的是小托峰写法
    // 在事件对象中会被转为全部小写的
    console.log(event.currentTarget.dataset.parentname)
  }
})

4.3.2 mark 自定义数据


  • 小程序进行事件传参的时候,除了使用 data-* 属性传递参数外,还可以使用 mark 标记传递参数

    mark 是一种自定义属性,可以在组件上添加,用于来识别具体触发事件的 target 节点。

    同时 mark 还可以用于承载一些自定义数据(类似于 dataset


  • markdataset 很相似,主要区别在于:
    • mark 会包含从触发事件的节点到根节点上所有的 mark: 属性值 (事件委托的)

    • dataset 仅包含触发事件那一个节点的 data- 属性值。


事件传参
  • wxml文件中,使用 mark:自定义属性 的方式将数据传递给事件处理函数
html
<!-- pages/index/index.wxml -->

<view bindtap="parentHandler" mark:parentid="1" mark:parentname="tom">
  <!-- 如果需要使用 mark 进行事件传参,需要使用 mark:自定义属性的方式进行参数传递 -->
  <!-- <button bindtap="btnHandler" mark:id="1" mark:name="tom">按钮</button> -->
  <button mark:id="1" mark:name="tom">按钮</button>
</view>
js
// cart.js
Page({

  // 按钮绑定的事件处理函数
  btnHandler (event) {
    console.log(event.mark.id)
    console.log(event.mark.name)
  },

  // view 绑定的事件处理函数
  parentHandler (event) {
    // 先点击蓝色区域 (不点击按钮)
    // 通过事件对象获取的是 view 身上绑定的数据

    // 先点击按钮 (不点击蓝色区域)
    // 通过事件对象获取到的是 触发事件的节点 以及 父节点身上所有的 mark 数据
    console.log(event)
  }

})

五、模版语法


5.1 声明&绑定数据


  • 小程序页面中使用的数据均需要在 Page() 方法的 data 对象中进行声明定义

  • 在将数据声明好以后,需要在 WXML 中绑定数据,

    数据绑定最简单的方式是使用 Mustache 语法(双大括号)(其实就是 VUE 的插值表达式)将变量包起来。


{{ }} 内部可以做一些简单的运算,支持如下几种方式:

  • 算数运算

  • 三元运算

  • 逻辑判断

  • 其它...


📌 注意事项:

​ 在 {{ }} 语法中,只能写表达式,不能写语句,也不能调用 js 相关的方法


  • 定义数据:
js
Page({
  // 页面的初始数据
  data: {
    num: 1
  }
    
  // code...
}

  • 使用数据:
html
<!-- 如果需要展示数据,在 wxml 中需要使用双大括号写法将变量进行包裹 -->

<!-- 展示内容 -->
<view>{{ school }}</view>
<view>{{ obj.name }}</view>

<!-- 绑定属性值,如果需要动态绑定一个变量,属性值也需要使用双大括号进行包裹 -->
<view id="{{ id }}">绑定属性值</view>

<!-- 如果属性值是布尔值,也需要使用双大括号进行包裹 -->
<checkbox checked="{{ isChecked }}" />

<!-- 算术运算 -->
<view>{{ id + 1 }}</view>
<view>{{ id - 1 }}</view>

<!-- 三元运算 -->
<view>{{ id === 1 ? '等于' : '不等于' }}</view>

<!-- 逻辑判断 -->
<view>{{ id === 1 }}</view>

<!-- 在双大括号写法内部,只能写表达式,不能写语句,也不能调用 js 的方法 -->
<!-- <view>{{ if (id === 1) {} }}</view> -->
<!-- <view>{{ for (const i = 0; i <= 10; i++) {} }}</view> -->
<!-- <view>{{ obj.name.toUpperCase() }}</view> -->

  • 例如:
js
// index.js
Page({

  // 在小程序页面中所需要使用的数据均来自于 data 对象
  data: {
    id: 1,
    isChecked: false,
    school: 'iraionly',
    obj: {
      name: 'tom'
    }
  }
})

5.2 声明&修改数据


  • 小程序中修改数据并不能直接进行赋值,而是要通过调用 this.setData 方法才能实现

    将需要修改的数据以 key:value 的形式传给 this.setData 方法。


  • this.setData 方法有两个作用:

    • 更新数据

    • 驱动视图更新


js
Page({
  // 页面的初始数据
  data: {
    num: 1
  },

  updateNum() {
    this.setData({
      // key 是需要修改的数据
      // value 是最新值
      num: this.data.num + 1
    })
  }
 
  // code...
}

5.3 setData 详解


5.3.1 修改对象类型数据


  • 在实际开发中,我们经常会在 data 中声明对象类型的数据,

    小程序中通过调用 setData 方法可以修改页面的数据,包括对象类型的数据。

    下面是修改对象类型数据的方法:


  • 定义一个对象
js
Page({
  // 定义页面中使用的数据
  data: {
    userInfo: {
      name: 'Tom',
      age: 10,
      gender: '男'
    }
  }
}

  • 修改对象中的单个属性
js
this.setData({
  'userInfo.name': 'Jerry'
})

  • 修改对象中的多个属性
js
// 修改对象中的多个属性
this.setData({
  'userInfo.name': 'Jerry',
  'userInfo.age': 100
})

  • 使用 ES6 的展开运算符

在修改对象类型的数据时,可以使用 ES6 的展开运算符先复制对象,然后利用新值对旧值覆盖的方式修改

js
const userInfo = {
  ...this.data.userInfo,
  name: 'Jerry',
  age: 100
}

// 修改对象中的多个属性
this.setData({
  // userInfo:userInfo
  // 键和值一样可以简写
  userInfo
})

  • 使用 Object.assign 方法合并对象

在修改对象类型的数据时,可以使用 Object.assign 方法将多个对象合并为一个对象

js
// 使用 Object.assign 方法将多个对象合并(从后往前合并)为一个对象
const userInfo = Object.assign(
  this.data.userInfo, 
  { name: 'Jerry' },
  { age: 100 }
)

// 修改对象中的多个属性
this.setData({
  // userInfo:userInfo
  // 键和值一样可以简写
  userInfo
})

  • 删除对象中的属性

在删除对象中的属性时,不能使用 delete 操作符,因为小程序的数据绑定机制不支持监听 delete 操作

js
// 使用展开运算符拷贝一份数据,产生一个新对象
const newUser = { ...this.data.userInfo }
// 使用 delete 删除新对象中的属性
delete newUser.age

this.setData({
  // 将新的对象进行赋值
  userInfo: newUser
})

📌 注意事项

  • 小程序的数据绑定机制只能监听到 setData 方法中修改的数据,无法监听到直接删除属性的操作,

    所以在删除对象属性时,需要先将对象复制一份再进行操作,然后再调用 setData 方法更新数据。


5.3.2 修改数组类型数据


  • 数组类型数据也是经常会使用的数据格式之一,下面是修改数组类型数据的方法:

  • 定义一个数组
js
Page({
  // 定义页面中使用的数据
  data: {
    animalList: ['Tom', 'Jerry', 'irai']
  }

  // code...
}

  • 使用数组的 concat 方法合并数组

在修改数组类型的数据时,可以使用数组的 concat 方法来合并数组

js
// 使用 concat 方法来合并数组
const newList = this.data.animalList.concat('Tyke')

// 使用 setData 进行赋值
this.setData({
  animalList: newList
})

  • 使用数组的 push 方法新增属性

在修改数组类型的数据时,可以使用数组的 push 方法来添加元素

js
// 使用数组的 push 方法来添加元素
this.data.animalList.push('Tyke')

// 使用 setData 进行赋值
this.setData({
  animalList: this.data.animalList
})

  • 使用 ES6 的展开运算符

在数组类型的数据时,可以使用 ES6 的展开运算符先复制数组,然后进行合并

js
// 使用 ES6 的展开运算符先复制数组,然后进行合并
const newList = [...this.data.animalList, 'Tyke']

// 使用 setData 进行赋值
this.setData({
  animalList: newList
})

  • 修改数组中某个元素的值:

利用索引的方式进行修改,但必须使用 wx:for 来进行渲染数组,否则会出现错误

js
this.setData({
  'animalList[2]': 'Tyke' 
})

  • 使用数组的 filter 方法删除元素
js
// 使用数组的 filter 方法来删除元素
// 在下面例子 newList 过滤出来的是数组中不等于 Tom 的元素
const newList = this.data.animalList.filter(item => item !== 'Tom')

// 使用 setData 进行赋值
this.setData({
  animalList: newList
})

5.4 简易双向绑定


  • WXML 中,普通属性的绑定是单向的,例如:
html
<input value="{{ num }}" />
  • 如果使用 this.setData 来更新 numnum 和输入框的中显示的值都会被更新为值。

    但如果用户修改了输入框里的值,却不会同时改变 data 中的 num

  • 也就是说:单向数据绑定指的是 :数据修改可以驱动视图,视图变化不能改变数据


  • 如果需要在用户输入的同时也将 data 中的数据修改 ,需要借助简易双向绑定机制。

    此时可以在对应项目之前加入 model: 前缀即可,例如:

html
<input model:value="{{ value }}" />
  • 如果使用 this.setData 来更新 numnum 和输入框的中显示的值都会被更新为值。

    如果手动修改了输入框的值, data 的数据也会随着改变。

    同时, WXML 中所有绑定了数据的位置也会被一同更新


📌简易双向绑定的属性值如下限制:

  • 只能是一个单一字段的绑定,例如:错误用法:<input model:value="值为 {{value}}" />

  • 尚不能写 data 路径,也就是不支持数组和对象,

    例如:错误用法:<input model:value="<span v-pre>{{a.b}}</span>" />


5.5 列表渲染


5.5.1 基本使用


  • 列表渲染:就是指通过循环遍历一个数组或对象,将其中的每个元素渲染到页面上

    只需要在组件上使用 wx:for 属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件


  • 默认数组当前项的变量名默认为 item

    默认数组的当前项的下标变量名默认为 index


  • 在使用 wx:for 对数组进行遍历的时候,建议加上 wx:key 属性,如不提供 wx:key

    会报一个 warning, 如果明确知道该列表是静态,即以后数据不会改变,

    或者不必关注其顺序,可以选择忽略。


  • wx:key 的值以两种形式提供:

    • 字符串:代表需要遍历的 arrayitem 的某个 property

      property 的值需要是列表中唯一的字符串或数字,且不能动态改变

    • 保留关键字 *this 代表在 for 循环中的 item 本身,

      item 本身是一个唯一的字符串或者数字时可以使用


  • 当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,

    而不是重新创建,以确保使组件保持自身的状态,并且 提高列表渲染时的效率


📌 注意事项

​ 在使用 wx:for 对数组进行遍历的时候,建议加上 wx:key 属性,否则控制台会报警告


  • 示例代码:
html
<!-- 如果需要进行列表渲染,需要使用 wx:for 属性 -->
<!-- 属性值需要使用双大括号进行包裹 -->
<!-- 每一项的变量名默认是 item -->
<!-- 每一项下标(索引)的变量名默认是 index -->

<!-- 如果渲染的是数组,item:数组的每一项,index:下标 -->
<!-- <view wx:for="{{ numList }}">{{ item }} - {{ index }}</view> -->

<!-- 如果渲染的是对象,item:对象属性的值,index:对象属性 -->
<!-- <view wx:for="{{ obj }}">{{ item }} - {{ index }}</view> -->

<!-- ------------------------ 关于 Key --------------------------------- -->

<!-- wx:key 提升性能 -->
<!-- wx:key 的属性值不需要使用双大括号进行包裹,直接写遍历的数组 中 item 的某个属性 -->

<!-- wx:key 属性值有两种添加形式 -->
<!-- 字符串,需要是遍历的数组 中 item 的某个属性,要求该属性是列表中唯一的字符串或者数字,不能进行动态改变 -->
<view wx:for="{{ fruitList }}" wx:key="id">{{ item.name }}</view>
<view wx:for="{{ fruitList }}" wx:key="index">{{ item.name }}</view>

<!-- 保留关键字 *this,*this 代表的是 item 本身,item 本身是唯一的字符串或者数字 -->
<view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
js
// profile.js
Page({

  data: {
    numList: [1, 2, 3],
    fruitList: [
      { id: 1, name: '🍎', price: 66 },
      { id: 2, name: '🍋', price: 77 },
      { id: 3, name: '🍅', price: 88 }
    ],
    obj: {
      name: 'tom',
      age: 10
    }
  }

})

5.5.2 使用进阶


  • 修改默认下标和变量名:可以使用 wx:for-itemwx:for-index

    • 使用 wx:for-item 可以指定数组当前元素的变量名

    • 使用 wx:for-index 可以指定数组当前下标的变量名


  • 示例:
html
<view wx:for="{{ animal }}" wx:for-item="itemName" wx:for-index="i">
  {{ itemName.name }} - {{ itemName.avatar }} - {{ i }}
</view>

  • 渲染多节点结构块:

如果需要渲染一个包含多节点的结构块,可以使用一个 <block/> 标签将多个组件包装起来

html
<block wx:for="{{ animal }}">
  <view>
    <span>{{ item.name }}</span>
    <span>{{ item.avatar }}</span>
  </view>
</block>
  • 注意: <block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。

5.6 条件渲染


  • 条件渲染主要用来控制页面结构的展示和隐藏,在微信小程序中实现条件渲染有两种方式:
    • 使用 wx:ifwx:elifwx:else 属性组
    • 使用 hidden 属性

  • 示例结构:
html
<view wx:if="{{condition}}"> True </view>
html
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view
html
<view hidden="{{condition}}"> True </view>

📌 wx:ifhidden 二者的区别:

  • wx:if :当条件为 true 时将内容渲染出来,否则元素不会进行渲染,通过移除/新增节点的方式来实现
  • hidden :当条件为 true 时会将内容隐藏,否则元素会显示内容,通过 display 样式属性来实现的

  • 示例代码:
html
<!-- 使用 wx:if、wx:elif、wx:else 属性组控制元素的隐藏和控制 -->
<view wx:if="{{ num === 1 }}">num 等于 {{ num }}</view>
<view wx:elif="{{ num === 2 }}">num 等于 {{ num }}</view>
<view wx:else>大于 2</view>

<view hidden="{{ num !== 1 && num !== 2 && num !== 3 && num < 3}}">
  {{ num < 3 ? 'num 等于' + num : '大于 2' }}
</view>

<button type="primary" bindtap="updateNum">修改数据</button>
js
Page({
  // 页面的初始数据
  data: {
    num: 1
  },
  
  // 更新数据
  updateNum() {
    this.setData({
      num: this.data.num + 1
    })
  }
    
  // code...
}