微信小程序官方DEMO解读

    我们在开始微信小程序开发的时候,对JS,HTML等前端知识一无所知,完完全全就是门外汉在尝试一个新的方向。

    在下载好开发工具,微信就已经提供了一个DEMO例子:

    

    从程序开发的角度来看这个陌生的目录结构,pages是存放页面的,utils是存放工具类的,而app开头的三个文件既然放在根目录级别,那么按理讲,应该是和配置有关。

    我们看app.js文件的内容:

//app.js
App({
  onLaunch: function () {
    //调用API从本地缓存中获取数据
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)
  },
  getUserInfo:function(cb){
    var that = this
    if(this.globalData.userInfo){
      typeof cb == "function" && cb(this.globalData.userInfo)
    }else{
      //调用登录接口
      wx.login({
        success: function () {
          wx.getUserInfo({
            success: function (res) {
              that.globalData.userInfo = res.userInfo
              typeof cb == "function" && cb(that.globalData.userInfo)
            }
          })
        }
      })
    }
  },
  globalData:{
    userInfo:null
  }
})

    根据官方文档的说明,这个文件用于编写微信小程序的页面逻辑。

    App函数用于注册一个小程序,onLanuch用于处理小程序的初始化,当小程序初始化完成的时候会调用一次。

    onLanuch这里的处理是取出wx中的log,然后再把当前日期添加进去。

    wx是一个命名空间,相当于一个库,它有很多公共方法。

    我们这里的操作和Android中使用SP(SharePreferences)是差不多的,wx有个本地缓存,这个缓存可以根据相关的key值取出对应的内容。这里有个有趣的语法:|| [],在javascript中,表示如果这个变量如果是undefined,null,NAN,false,0中的任意一种,就设置为一个空的数组,可以理解为?:的用法。然后调用javascript的unshift方法,把当前日期插入数组的第一个元素。

    getUserInfo是自定义的函数,传入一个函数作为参数,而且这里还定义了全局对象globalData,它有一个字段userInfo,初始值为null,通过判断userInfo是否为空,非空则在cb为函数类型的情况下调用cb,空的情况下,则通过调用wx.login方法,在success的情况下调用wx的getUserInfo获取userInfo。

    这个js文件已经大概的展示了javascript的很多基本语法,因为javascript是动态语言,它是面向函数语言,因此和java这种面向对象语言在语义实现上,差别相当大,我们可以简单的理解为java操作的是对象,而javascript操作的是函数,而wx.login中的参数就是一个对象,因此用{}包起来,函数其实也是一个对象,所以它后面同样也有{},这个对象就是loginObject,在它success的时候调用wx.getUserInfo函数。

    无论是面向对象还是面向函数,本质上都是体现开发人员理解问题的思路,只是语义实现上不同而已,毕竟javascript和java要解决的问题所在的领域有着相当大的差异。

    我们再看看app.json这个文件:

{
  "pages":[
    "pages/index/index",
    "pages/logs/logs"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle":"black"
  }
}

   根据文档的解释,这个文件是对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多tab等。

   pages用于设置页面的路径,是一个数组,我们这个DEMO的页面只有两个:index和logs,其中第一个元素,index,是小程序的第一个页面,每次新增或者删除某个页面,都要在这里进行修改。

   window用来设置默认的窗口的属性,显然app.json是设置整个小程序窗口的默认属性,会被具体页面的相关属性覆盖。

   最后是app.wxss:   

/**app.wxss**/
.container {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 200rpx 0;
  box-sizing: border-box;
} 

   这个文件其实就是CSS文件,不过微信自己本身做了一些处理,这个文件的内容就是设置了container这个节点视图的公共属性。

   从这三个根目录的文件,我们也大概知道一个小程序的页面的组成结构,但是还少了一个文件,那就是页面视图的文件。

   DEMO的logs页面就包含了四个文件:.json,.js,.wxss和.wxml。

   我们看一下logs.wxml这个文件:

<!--logs.wxml-->
<view class="container log-list">
  <block wx:for="{{logs}}" wx:for-item="log" wx:key="*this">
    <text class="log-item">{{index + 1}}. {{log}}</text>
  </block>
</view>

   <view/>表示一个视图的节点,相当于Android中的ViewGroup,然后通过class赋予这个view一个或多个类名,wxss就是通过这个class来控制对应视图的渲染效果,这里是两个类名:container和log-list,目的就是在一些属性上覆盖.container的设置。

   <block/>表示这是一个多节点的视图,也就是列表组件,通过wx:for表示这个列表组件的数组来源,在微信的设计中,{{}}表示数据绑定,这里绑定了logs这个数组作为数据来源。

    wx:for-item指定了当前数组的元素名,我们可以理解为java中的增强for的用法:for(item : array)。

    wx:key表示一个唯一标识,因为这个数组是动态数组,会不断增加自己本身的长度,而我们不希望已经创建好的元素在重新渲染的时候会被修改,因此通过wx:key指定*this,表示for循环中的item只是被重新排序,而不是被重新创建,这样是为了提高渲染的效率。

    最后我们看一下<block/>里面的子视图,是一个text视图,渲染的内容是数组中的元素,默认数组的下标变量名是index,元素名称是item,因此{{index + 1}}表示取当前元素的下标,因为下标是从0开始,所以这里加1来和人类世界中下标从1开始的共识达成一致,而log就是logs中的元素的内容,因为我们已经通过wx:for-item指定了item的名称为log。

    logs目录下的logs.js也是相当有意思的:

//logs.js
var util = require('../../utils/util.js')
Page({
  data: {
    logs: []
  },
  onLoad: function () {
    this.setData({
      logs: (wx.getStorageSync('logs') || []).map(function (log) {
        return util.formatTime(new Date(log))
      })
    })
  }
})

    util.js是我们utils目录的文件,这里的"../../utils/util.js"是通过相对路径来导入这个js文件,按照我们的理解,相当于import一个库。

    Page函数是用来注册一个页面,data声明页面的初始数据,这里是一个logs数组,而data里面的数据是通过json传递到页面,因此这里面的格式要确保完全符合json格式。然后调用onLoad函数,在加载页面的时候通过setData将逻辑层的数据传递到视图层,也就是所谓的数据绑定,并且改变this.data的内容。

    setData中的函数通过调用数组的map函数,将数组的内容重新映射成新的内容,这里相当于初始化数组,map就是用来对数组内容进行赋值的。

    我们看一下util.js的内容:

function formatTime(date) {
  var year = date.getFullYear()
  var month = date.getMonth() + 1
  var day = date.getDate()

  var hour = date.getHours()
  var minute = date.getMinutes()
  var second = date.getSeconds()


  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
}

function formatNumber(n) {
  n = n.toString()
  return n[1] ? n : '0' + n
}

module.exports = {
  formatTime: formatTime
}

     这里的内容很简单,就是对log的日期进行格式化,不过我们注意到最后的内容,module.exports执行了赋值操作。

     这个其实是和require搭配的,require返回的就是这个module.exports,然后定义了一个formatTime对象,也就是可以调用的对象,而这个对象就是formatTime函数。

    通过require和module.exports来确定了这个js文件暴露出来的API。

    我们现在来关注一个很重要的语法:=和:这两个操作符到底是怎么用的。

    =就是赋值操作符,这个毋庸置疑,而:其实也是赋值操作,对于a:function,其实表示key值为a的value的内容为function,所以formatTime:formatTime就是表示util.formatTime这个属性对应的是formatTime函数。

    我们再来看一下index目录下的文件。

    先看一下index.js:

//index.js
//获取应用实例
var app = getApp()
Page({
  data: {
    motto: 'Hell World',
    userInfo: {}
  },
  //事件处理函数
  bindViewTap: function() {
    wx.navigateTo({
      url: '../logs/logs'
    })
  },

  onLoad: function () {
    console.log('onLoad')
    var that = this
    //调用应用实例的方法获取全局数据
    app.getUserInfo(function(userInfo){
      //更新数据
      that.setData({
        userInfo:userInfo
      })
    })
  }
})

    我们通过getApp函数来获取小程序实例,因为我们需要调用app.js中的函数,这里调用的是getUserInfo,可以理解为app.js中定义的方法都是公共方法,因为这里并没有require和module.exports的调用。

    这里有一个新的知识点:bindViewTap。

    这个是一个事件处理函数,事件是逻辑层到视图层的通讯方式,将用户的行为反馈到逻辑层进行处理,bindViewTap这个事件函数是用户在点击时候触发的,相当于onClick,wx.navigateTo表示跳转到url指定的页面。

     我们看一下inde.wxml文件就知道了:     

<!--index.wxml-->
<view class="container">
  <view  bindtap="bindViewTap" class="userinfo">
    <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
    <text class="userinfo-nickname">{{userInfo.nickName}}</text>
  </view>
  <view class="usermotto">
    <text class="user-motto">{{motto}}</text>
  </view>
 </view>

    要通过bindtap指定点击事件函数。

    事件分为两种:冒泡事件和非冒泡事件,冒泡事件会把事件往上传递,而非冒泡则反之。冒泡事件前缀是bind,而非冒泡事件是catch。

    通过对这个DEMO,我们大概了解到小程序的目录结构,和一些相关的基础知识,后面会在具体的开发工作中继续补充相关的知识。

posted @ 2017-06-05 17:57  文酱  阅读(4117)  评论(0编辑  收藏  举报