WeChat 文章列表页面(二)

2024-03-19 14:14:59 浏览数 (1)

UnsplashUnsplash

本次的系列博文的知识点讲解和代码,主要是来自于 七月老师 的书籍《微信小程序开发:入门与实践》,由个人总结并编写,关于更多微信小程序开发中的各项技能,以及常见问题的解决方案,还请大家购买书籍进行学习实践,该系列博文的发布已得到七月老师的授权许可

授权许可授权许可

我们在 WeChat 文章列表页面(一) 中,已经完成了文章列表页面了,效果图如下所示

文章列表页面文章列表页面

Page 页面的生命周期

post.js 文件默认包含的代码如下所示

代码语言:javascript复制
Page({

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

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
  
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {
  
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
  
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {
  
  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
  
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
  
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
  
  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {
  
  }
})

可以看到,整个页面执行了一个 Page({...}) 方法,参数是一个 Object 对象,用来指定页面的初始数据 (data)、生命周期函数 (on 开头的函数)、事件处理函数等

一个页面从创建到卸载,会经历以下 5 个周期:加载、显示、渲染、隐藏、卸载,MINA 框架分别提供了 5 个生命周期函数来监听这 5 个特定的生命周期,以方便开发者可以在这些特定的时刻执行一些自己的代码逻辑,它们分别是:

  • onLoad 监听页面加载,一个页面只会调用一次
  • onShow 监听页面显示,每次打开页面都会调用
  • onReady 监听页面初次渲染完成,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互
  • onHide 监听页面隐藏
  • onUnload 监听页面卸载

除了以上 5 个生命周期函数之外,还包括以下 3 个小程序特定事件的处理函数:

  • onPullDownRefresh 监听用户下拉动作的事件处理函数
  • onReachBottom 页面上拉触底事件的处理函数
  • onShareAppMessage 用户点击右上角分享

我们接下来通过控制台打印的方式,来看下生命周期函数及事件处理函数的触发时机,也可以通过打断点的方式,进行调试,具体代码如下所示:

代码语言:javascript复制
Page({
  data: {
  
  },
  onLoad: function (options) {
    console.log("onLoad:页面被加载");
  },
  onReady: function () {
    console.log("onReady:页面被渲染");    
  },
  onShow: function () {
    console.log("onShow:页面被显示");    
  },
  onHide: function () {
    console.log("onHide:页面被隐藏");    
  },
  onUnload: function () {
    console.log("onUnload:页面被卸载");    
  },
  onPullDownRefresh: function () {
     console.log("onPullDownRefres:监听用户下拉动作");    
  },
  onReachBottom: function () {
       console.log("onReachBottom:页面上拉触底事件");    
  },
  onShareAppMessage: function () {
       console.log("onShareAppMessage:用户点击右上角分享");   
  }
})
运行结果运行结果

可以看到,一个页面要正常显示,必须经过以上 3 个生命周期:加载、显示、渲染,至于 onHide 和 onUnload 函数,以及 3 个特定事件的处理函数,它们的触发都需要执行一些 API 操作,这些我们到之后的部分再做介绍

官方文档中,也是给出 Page 实例生命周期的图解,同时也告诉我们,以下内容你不需要立马完全弄明白,不过以后它会有帮助,所以在这里建议大家的是,在遇到问题或者业务需要时,再回过头来研究这张完整的生命周期图更有意义

页面生命周期页面生命周期

数据绑定

在真实项目中,业务数据通常都放置在自己的服务器中,然后通过 HTTP 请求来访问服务器提供的 RESTFUI API,从而实现数据的获取

接下来,我们尝试将编码在 post.wxml 文件里的数据移植到 post.js 中,在 post.js 中加入一个临时变量 postData 来模拟文章数据,并将上一小节中测试生命周期的代码移除,编写完成后的代码如下:

代码语言:javascript复制
Page({
  data: {
    date: "Jan 28 2017",
    title: "小时候的冰棍儿与雪糕",
    postImg: "/images/post/post-4.jpg",
    avatar: "/images/avatar/avatar-5.png",
    content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
    readingNum: 92,
    collectionNum: 108,
    commentNum: 7
  },
})

如果是传统的网页开发,我们会想到,先获取 HTML 文档的 DOM,然后对 DOM 标签进行复制,从而实现数据的显示,但在小程序中,是没有 DOM 结构的,无法通过这样的方式,将数据“填充”到页面当中

在现在流行的 MVC 或者 MVVM 框架中,如 AngularJS、Vue.js 中,都有数据绑定的概念,小程序也是借鉴了这些流行框架的思想,采用数据绑定的机制来做数据的初始化和更新

不同于 AngularJS 的双向数据绑定,小程序仅实现了单向数据绑定,即只支持从逻辑层传递到渲染层的数据绑定,反之则不可以

小程序使用 Page 方法参数里的 data 变量作为数据绑定的桥梁,data 里已经被我们放置了一些数据,这些直接写在 data 里的数据,被称为数据绑定的初始化数据

需要注意的是,数据绑定有以下两种:

  • 一种是初始化数据的数据绑定,通常将这些数据直接写在 Page 方法参数的 data 对象下面
  • 另外一种是使用 setData 方法来做数据绑定,这种方式也可以理解为数据更新,这样的数据更新将引起页面的 Rerender(重新渲染),参考上一小节的页面生命周期图

接下来,我们对 post.wxml 文件做一些改动,使用 Mustache 语法的双大括号 {{}} 在 wxml 组件里进行数据的绑定,凡是对标签属性做绑定的,一定要记得加上双引号,代码如下:

代码语言:javascript复制
<view class="post-container">
  <view class="post-author-date">
    <image src="{{avatar}}" />
    <text>{{date}}</text>
  </view>
  <text class="post-title">{{title}}</text>
  <image class="post-image" src="{{postImg}}" mode="aspectFill" />
  <text class="post-content">{{content}}</text>
  <view class="post-like">
    <image src="/images/icon/wx_app_collect.png" />
    <text>{{collectionNum}}</text>
    <image src="/images/icon/wx_app_view.png" />
    <text>{{readingNum}}</text>
    <image src="/images/icon/wx_app_message.png" />
    <text>{{commentNum}}</text>
  </view>
</view>
运行结果运行结果

我们通过页面生命周期图解,来解释一下初始化数据绑定的过程,当页面执行了 onShow 函数后,逻辑层会收到一个通知 (Notify);随后逻辑层会将 data 对象以 json 的形式发送到 View 视图层 (Send Initial Data),视图层接受初始化数据后,开始渲染并显示初始化数据 (First Render),最终将数据呈现在开发者的面前

我们打开“编辑”选项卡,点击 AppData 就能够看到数据绑定变量,如下图所示

post 页面在 AppData 面板中的数据绑定情况post 页面在 AppData 面板中的数据绑定情况

点击 Tree 选项,切换成 Code,数据将以 json 的形式呈现,如下图所示

以 json 的格式呈现数据以 json 的格式呈现数据

如果 data 对象的属性较为复杂,包括对象和数组,那需要相应的调整 wxml 文件,可以看下面两张图进行理解

较为复杂的 data 对象较为复杂的 data 对象
根据 data 对象结构调整的 wxml根据 data 对象结构调整的 wxml

数据绑定更新

通过 setData 函数来进行数据绑定,这种方式可以理解为“数据更新”,setData 方法位于 Page 对象的原型链上:Page.prototype.setData,在大多数的情况下,我们使用 this.setData 的方式来调用这个方法

setData 的参数接受一个对象,以 key 和 value 的形式将 this.data 中的 key 对应的值设置成 value,这句话需要注意两点:① setData 会改变 this.data 变量里相同 key 的值;② setData 执行后会通知逻辑层执行 Rerender,并立刻重新渲染视图

代码语言:javascript复制
Page({
  data: {
    object: {
      date: "Jan 28 2017"
    },
    title: "小时候的冰棍儿与雪糕",
    postImg: "/images/post/post-4.jpg",
    avatar: "/images/avatar/avatar-5.png",
    content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
    readingNum: 92,
    collectionNum: {
      array: [108]
    },
    commentNum: 7
  },
  onLoad:function(){
    this.setData({
      title: "一根雪糕的经济学原理"
    })
  }
})
运行结果运行结果

可以看到,第一篇文章的标题由 data 里所设置的 title:"小时候的冰棍儿与雪糕",被更改成了“一根雪糕的经济学原理”,key 可以使用字符串来表示,可以看下面 3 个例子

代码语言:javascript复制
onLoad:function(){
  this.setData({
    "title": "一根雪糕的经济学原理"
  })
}
代码语言:javascript复制
onLoad:function(){
  this.setData({
    "collectionNum.array[0]": 66
  })
}
代码语言:javascript复制
onLoad:function(){
  this.setData({
    "object.date": "Jan 28 2012"
  })
}

使用 this.setData 所绑定或者更新的数据,并不要求在 this.data 中已预先定义,接下来,我们将 post.js 文件修改成使用 setData 函数进行数据绑定

代码语言:javascript复制
Page({
  data: {

  },
  onLoad:function(){
    var iceCreamData = {
      object: {
        date: "Jan 28 2017"
      },
      title: "小时候的冰棍儿与雪糕",
      postImg: "/images/post/post-4.jpg",
      avatar: "/images/avatar/avatar-5.png",
      content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
      readingNum: 92,
      collectionNum: {
        array: [108]
      },
      commentNum: 7
    }
    this.setData({
      postData: iceCreamData
    })
  }
})

相应的,wxml 文件修改如下:

代码语言:javascript复制
<view class="post-container">
  <view class="post-author-date">
    <image src="{{postData.avatar}}" />
    <text>{{postData.object.date}}</text>
  </view>
  <text class="post-title">{{postData.title}}</text>
  <image class="post-image" src="{{postData.postImg}}" mode="aspectFill" />
  <text class="post-content">{{postData.content}}</text>
  <view class="post-like">
    <image src="/images/icon/wx_app_collect.png" />
    <text>{{postData.collectionNum.array[0]}}</text>
    <image src="/images/icon/wx_app_view.png" />
    <text>{{postData.readingNum}}</text>
    <image src="/images/icon/wx_app_message.png" />
    <text>{{postData.commentNum}}</text>
  </view>
</view>

此时,我们来看一下 AppData 面板中的数据绑定情况

AppData 面板中的数据绑定情况AppData 面板中的数据绑定情况

目前,关于数据绑定的错误,小程序不会给出任何的错误提示,如果你发现整个页面是空白的有没有错误消息,多半是数据绑定出了问题,而这个时候,AppData 面板就是我们最好的调试工具

最后我们再将另外两篇的文章的数据提取到 post.js 文件中,同一篇文章的数据组成一个数组

代码语言:javascript复制
Page({
  data: {

  },
  onLoad:function(){
    var postList = [{
      object: {
        date: "Jan 28 2017"
      },
      title: "小时候的冰棍儿与雪糕",
      postImg: "/images/post/post-4.jpg",
      avatar: "/images/avatar/avatar-5.png",
      content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
      readingNum: 92,
      collectionNum: {
        array: [108]
      },
      commentNum: 7
    },
    {
      object: {
        date: "Jan 9 2017"
      },
      title: "从童年呼啸而过的火车",
      postImg: "/images/post/post-5.jpg",
      avatar: "/images/avatar/avatar-1.png",
      content: "小时候,家的后面有一条铁路。听说从南方北上的火车都必须经过这条铁路。火车大多在晚上经过,但也不定时只有在夜深人静的时候,火车的声音才能从远方传来...",
      readingNum: 92,
      collectionNum: {
        array: [108]
      },
      commentNum: 7
    },
    {
      object: {
        date: "Jan 29 2017"
      },
      title: "记忆里的春节",
      postImg: "/images/post/post-1.jpg",
      avatar: "/images/avatar/avatar-3.png",
      content: "年少时,有几样东西,是春节里必不可少的:烟花、心意、凉茶、压岁钱、饺子。年分大小年,有的地方是腊月二十三过小年,有的地方是腊月二十四...",
      readingNum: 92,
      collectionNum: {
        array: [108]
      },
      commentNum: 7
    }
  ]
  this.setData({
      postList: postList
    })
  }
})

列表渲染 wx:for

上一小节,我们已经把三篇文章的数据提取到 post.js 文件中了,但是 wxml 文件我们并没有改写,我们固然可以像改写第一篇文章一样,依次修改其他两篇文章的 {{}} 绑定,但假如这里有 100 篇文章呢?

小程序提供了一个 wxml 组件的 for 循环,称为列表循环,它具体指的是,在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件,默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item

代码语言:javascript复制
<view wx:for="{{array}}">
  {{index}}: {{item.message}}
</view>

Page({
  data: {
    array: [{
      message: 'foo',
    }, {
      message: 'bar'
    }]
  }
})
运行结果运行结果
代码语言:javascript复制
<block wx:for="{{postList}}" wx:for-index="idx" wx:for-item="item">
  <view class="post-container">
    <view class="post-author-date">
      <image src="{{item.avatar}}" />
      <text>{{item.object.date}}</text>
    </view>
    <text class="post-title">{{item.title}}</text>
    <image class="post-image" src="{{item.postImg}}" mode="aspectFill" />
    <text class="post-content">{{item.content}}</text>
    <view class="post-like">
      <image src="/images/icon/wx_app_collect.png" />
      <text>{{item.collectionNum.array[0]}}</text>
      <image src="/images/icon/wx_app_view.png" />
      <text>{{item.readingNum}}</text>
      <image src="/images/icon/wx_app_message.png" />
      <text>{{item.commentNum}}</text>
    </view>
  </view>
</block>
运行结果运行结果

在这里的 <block> 标签并没有实际意义,可以理解为常见编程语言里的括号

事件

在讲述事件之前,我们先将导航栏的颜色修改成其他颜色,只需在 app.json 中修改即可,并将 welcome 页面重新调整为启动页面

代码语言:javascript复制
{
  "pages": [
    "pages/welcome/welcome",
    "pages/post/post"
  ],
  "window": {
    "navigationBarBackgroundColor": "#4A6141"
  }
}

但也别忘记,把 welcome 页面的导航栏颜色修改回来,welcome.json 文件里的代码如下:

代码语言:javascript复制
{
    "navigationBarBackgroundColor": "#b3d4db"
}

那么页面的 json 文件配置和 app.json 文件的配置有什么不同?

  • 页面的 json 文件只能够配置和 window 相关的属性,但 app.json 除了可以配置 window 外,还可以配置 pages、tabBar 等选项
  • 页面的 json 配置不需要像 app.json 那样,加上 window 这个对象,直接编写 window 下面的配置项即可

接下来,我们想要从 welcome 页面跳转到 post 页面,需要使用事件来响应点击“开启小程序之旅”这个动作,要实现这个操作,需要做两件事情:

  • 在组件上注册事件,注册事件将告诉小程序,我们要监听哪个组件的什么事件,而在这里,就是监听“开启小程序之旅”这个组件的 tap 事件
  • 在 js 中编写事件处理函数响应事件,也就是说,监听到事件后,需要编写自己的业务,我们在这里将调用 MINA 框架的导航 API,让 welcome 页面跳转到 post 页面

更改 welcome.wxml 页面的代码,具体代码如下所示:

代码语言:javascript复制
<view class="container">
  <image class="avatar" src="/images/avatar/niangao.png"></image>
  <text class="motto">Hello,Nian糕</text>
  <view class="journey-container" catchtap="onTapJump">
    <text class="journey">开起小程序之旅</text>
  </view>
</view>

这里和之前的代码并没有太大的改动,仅仅是在 class="journey-container" 的这个 view 组件上添加了一个 catchtap="onTapJump" 的事件绑定,需要注意的是,tap 是一个冒泡事件,常见的冒泡事件类型还有以下几种:

  • touchstart 手指触摸动作开始
  • touchmove 手指触摸后移动
  • touchcancel 手指触摸动作被打断,如来电提醒、弹窗
  • touchend 手指触摸动作结束
  • tap 手指触摸后马上离开
  • longtap 手指触摸后,超过 350ms 再离开

小程序在 wxml 组件里注册事件时,不可以直接使用 tap="function" 或 touch="function",而是需要在事件名之前添加 catch 或者 bind 前缀,catch 将组织事件继续向父节点传播,而 bind 不会阻止事件的传播

当用户点击之后,会执行一个 onTapJump 的函数,而该函数需要在页面的 js 中定义

代码语言:javascript复制
Page({
  onTapJump:function(event){
    wx.redirectTo({
      url:"../post/post",
      success:function(){
        console.log("jump success")
      },
      fail:function(){
        console.log("jump failed")
      },
      complete:function(){
        console.log("jump complete")
      }
    });
  }
})
运行结果运行结果

微信小程序一共提供了 5 个导航 API,以帮助开发者实现页面的跳转

  • wx.redirectTo 关闭当前页面,跳转到应用内的某个页面
  • wx.navigateTo 保留当前页面,跳转到应用内的某个页面,使用 wx.navigateBack 可以返回到原页面
  • wx.switchTap 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
  • wx.navigateBack 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
  • wx.reLaunch 关闭所有页面,打开到应用内的某个页面

这 5 个方法都接受一个 Object 对象作为参数,除了 wx.navigateBack 之外,其余 4 个方法的 Object 参数还可以接受 3 个方法,分别是:

  • success 跳转页面成功时 MINA 框架将调用此函数
  • fail 跳转页面失败时 MINA 框架将调用此函数
  • complete 无论成功或者失败,MINA 框架都将调用此函数

我们选取了 redirectTo 方法进行页面跳转,但是 redirectTo 方法将关闭当前页面并将页面卸载,无法从 post 页面返回到 welcome 页面当中,所以我们在这里选取 navigateTo 的方法进行页面的跳转,在 post 页面的左上角会有一个返回按钮,至于两个页面之间跳转时,两个页面是被卸载还是被隐藏,大家可自行在 welcome.js 文件和 post.js 文件当中添加下面的代码进行分析理解

代码语言:javascript复制
onUnload: function (event) {
    console.log("page is unload")
},
onHide: function (event) {
    console.log("page is hide")
},
运行结果运行结果

该章节的内容到这里就全部结束了,源码我已经发到了 GitHub WeChat_03 上了,有需要的同学可自行下载

0 人点赞