带你读《微信小程序商城开发:界面设计实战》之二:小程序基础知识-阿里云开发者社区

开发者社区> 华章出版社> 正文
登录阅读全文

带你读《微信小程序商城开发:界面设计实战》之二:小程序基础知识

简介: 本书是讲解微信小程序前端界面设计的入门书籍,有详细的理论知识、布局分析、逻辑分析,还有丰富的实战案例以及详细的代码解说,具有很强的实用性。

点击查看第一章
点击查看第三章
第2章

小程序基础知识

第1章为小程序入门的各项事宜,接下来本章将介绍微信小程序开发的基础知识:项目有哪些配置文件,微信小程序的各种配置,WXSS样式语言,逻辑层.js脚本,WXML视图层开发等。

2.1 项目配置文件

可以在项目根目录使用project.config.json文件(参见1.3.1节)对项目进行配置,项目配置文件的内容参见表2-1。

表2-1 项目配置文件

image.png

其中,compileType的有效值如下。

  • miniprogram:当前为普通小程序项目。
  • plugin:当前为小程序插件项目。

setting中可以指定的内容如下:
image.png
scripts中指定自定义预处理的命令如下。

  • beforeCompile:编译前预处理命令。
  • beforePreview:预览前预处理命令。
  • beforeUpload:上传前预处理命令。

packOptions用于配置项目在打包过程中的选项。打包是预览、上传时对项目进行的必须步骤。目前可以指定packOptions.ignore字段,忽略配置打包时符合指定规则的文件或文件夹,以跳过打包过程,这些文件或文件夹将不会出现在预览或上传的结果内。
packOptions.ignore为一对象数组,对象元素类型如下:
image.png
其中,type可以取值为folder、file、suffix、prefix、regexp、glob,分别对应文件夹、文件、后缀、前缀、正则表达式、Glob规则。所有规则值都会自动忽略大小写。
value字段的值若表示文件或文件夹路径,以小程序目录(miniprogramRoot)为根目录。regexp、glob仅1.02.1809260及以上版本工具支持。
配置示例代码如下:

{
  "packOptions": {
    "ignore": [{
      "type": "file",
      "value": "test/test.js"
    }, {
      "type": "folder",
      "value": "test"
    }, {
      "type": "suffix",
      "value": ".webp"
    }, {
      "type": "prefix",
      "value": "test-"
    }, {
      "type": "glob",
      "value": "test/**/*.js"
    }, {
      "type": "regexp",
      "value": "\\.jsx$"
    }]
  }
}

这部分设置的更改可能需要重新打开项目才能生效。
debugOptions用于配置在对项目代码进行调试时的选项。目前可以指定debugOptions.hidedInDevtools字段,用于配置是否显示调试器的源代码。
hidedInDevtools的配置规则和packOptions.ignore是一致的。当某个.js文件符合此规则时,调试器Sources面板中此文件源代码正文内容将被隐藏,显示代码示例如下:

// xxx.js has been hided by project.config.json

注:配置此规则后,可能需要关闭并重新打开项目才能看到效果。
项目配置代码示例如下:

{
  "miniprogramRoot": "./src",
  "qcloudRoot": "./svr",
  "setting": {
    "postcss": true,
    "es6": true,
    "minified": true,
    "urlCheck": false
  },
  "packOptions": {
    "ignore": []
  },
  "debugOptions": {}
}

2.2 全局配置和页面配置

每个微信小程序项目都有一个全局配置文件和多个页面配置文件。全局配置文件针对整个微信小程序项目的相关配置信息;页面配置文件只针对对应的页面,每个微信小程序都有一个对应的页面配置文件。全局配置文件和页面配置文件如果有相同的配置项目,页面配置文件的优先级高于全局配置文件,也就是以页面配置文件的效果为主。

2.2.1 全局配置

我们利用小程序根目录下的app.json文件对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多tab等。
每个微信小程序项目只有一个全局配置文件。下面是一个包含了部分常用配置选项的app.json:

{
  "pages": [
    "pages/index/index",
    "pages/logs/index"
  ],
  "window": {
    "navigationBarTitleText": "Demo"
  },
  "tabBar": {
    "list": [{
      "pagePath": "pages/index/index",
      "text": "首页"
    }, {
      "pagePath": "pages/logs/logs",
      "text": "日志"
    }]
  },
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  },
  "debug": true,
  "navigateToMiniProgramAppIdList": [
    "wxe5f52902cf4de896"
  ]
}

app.json配置项列表参见表2-2。

表2-2 app.json配置项列表

image.png
image.png
(1)pages
pages用于指定小程序由哪些页面组成,是一个数组,数组中每一项都对应一个页面的“路径+文件名”信息。文件名不需要写文件后缀,开发框架会自动去寻找对应位置的.json、.js、.wxml、.wxss四个文件进行处理。数组的第一项代表小程序的初始页面(首页)。小程序中新增/减少页面,都需要对pages数组进行修改。例如,如下开发目录中:
├── app.js
├── app.json
├── app.wxss
├── pages
│ │── index
│ │ ├── index.wxml
│ │ ├── index.js
│ │ ├── index.json
│ │ └── index.wxss
│ └── logs
│ ├── logs.wxml
│ └── logs.js
└── utils
要在app.json中编写页面,则需要配置pages数组,代码如下所示。

{
  "pages":[
    "pages/index/index",
    "pages/logs/logs"
  ]
}

(2)window
window用于设置小程序的状态栏、导航条、标题、窗口背景色等,属性参见表2-3。

表2-3 window属性

image.png
其中,HexColor为十六进制颜色值,#ffffff表示白色,#000000表示黑色。
navigationStyle只在app.json中生效。开启custom后,低版本客户端需要做好兼容。开发者工具基础库版本切到客户端6.7.2版本开始,navigationStyle: custom对组件无效。

image.png

app.json示例代码如下,界面示例如图2-1所示:

{
  "window":{
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "微信接口功能演示",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "light"
  }
}

(3)tabBar
tabBar用于设置小程序多tab。例如,客户端窗口的底部或顶部有tab栏可以切换页面,可以通过tabBar配置项指定tab栏的表现,以及tab切换时显示的对应页面。tabBar属性参见表2-4。

表2-4 tabBar属性

image.png

其中,list接受一个数组,只能配置最少2个、最多5个tab。tab按数组的顺序排序,每个项都是一个对象,其属性值如下:
image.png
当属性iconPath和selectedIconPath的postion为top时,不显示icon。list属性如图2-2所示。
image.png

图2-2 list属性示例

(4)networkTimeout
networkTimeout用于指定各类网络请求的超时时间,单位均为毫秒,networkTimeout属性参见表2-5。

表2-5 networkTimeout属性

image.png
(5)debug
可以在开发者工具中开启debug模式,在开发者工具的控制台面板,调试信息以info的形式给出,其信息有Page的注册、页面路由、数据更新、事件触发等,可以帮助开发者快速定位一些常见的问题。
(6)functionalPages
基础库2.1.0开始支持,低版本需做兼容处理。启用插件功能页时,插件所有者小程序需要将functionalPages设置为true。
(7)subpackages
微信客户端6.6.0、基础库1.7.3及以上版本支持。启用分包加载时,声明项目分包结构。写成subPackages也支持。
(8)workers
使用Worker处理多线程任务时,设置Worker代码放置的目录。
(9)requiredBackgroundModes
微信客户端6.7.2及以上版本支持。声明需要后台运行的能力,类型为数组。目前支持以下项目:Audio后台音乐播放,代码示例如下:

{
  "pages": ["pages/index/index"],
  "requiredBackgroundModes": ["audio"]
}

此处声明了后台运行的接口,开发版和体验版上可以直接生效,正式版还需通过审核。
(10)plugins
基础库1.9.6开始支持,低版本需做兼容处理。声明小程序需要使用的插件。
(11)preloadRule
基础库2.3.0开始支持,低版本需做兼容处理。声明分包预下载的规则。
(12)resizable
基础库2.3.0开始支持,低版本需做兼容处理。在iPad上运行的小程序可以设置支持屏幕旋转。
(13)navigateToMiniProgramAppIdList
基础库2.4.0开始支持,低版本需做兼容处理。当小程序需要使用wx.navigateToMini-Program接口跳转到其他小程序时,需要先在配置文件中声明需要跳转的小程序的AppID列表,最多允许填写10个。

2.2.2 页面配置

每一个小程序的页面可以使用.json文件对本页面的窗口表现进行配置。页面配置只能设置app.json中部分window配置项的内容,页面中配置项会覆盖app.json的window中相同的配置项,页面配置属性参见表2-6。
配置样例代码如下:

{
  "navigationBarBackgroundColor": "#ffffff",
  "navigationBarTextStyle": "black",
  "navigationBarTitleText": "微信接口功能演示",
  "backgroundColor": "#eeeeee",
  "backgroundTextStyle": "light"
}

表2-6 页面配置属性

image.png
页面的.json只能设置window相关的配置项,以决定本页面的窗口表现,所以无须写window这个键。

2.3 WXSS样式语言

WXSS(WeiXin Style Sheets)是一套样式语言,用于描述WXML的组件样式。WXSS用来决定WXML的组件应该怎么显示。为了适应广大的前端开发者,WXSS具有CSS大部分特性。同时,为了更适合开发微信小程序,WXSS对CSS进行了修改和扩充。
与CSS相比,在微信小程序WXSS扩展的特性有:

  • 尺寸单位。
  • 样式导入。
  • 全局样式和局部样式。

内联样式和选择器沿用了CSS的功能写法。
1.尺寸单位
尺寸单位为rpx(responsive pixel),可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在iPhone6上,屏幕宽度为375px,共有750个物理像素,则750rpx=375px= 750物理像素,1rpx=0.5px=1物理像素,不同设备的换算方式如下:
image.png
在开发微信小程序时,建议设计师使用iPhone6作为视觉稿的标准。
在较小的屏幕上不可避免会有一些毛刺,在开发时尽量避免这种情况。
2.样式导入
可以使用@import语句导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用分号“;”表示语句结束。代码示例如下:

/** common.wxss **/
.small-p {
  padding:5px;
}

/** app.wxss **/
@import "common.wxss";
.middle-p {
  padding:15px;
}

3.全局样式与局部样式
定义在app.wxss中的样式为全局样式,作用于每一个页面。在page的.wxss文件中定义的样式为局部样式,只作用于对应的页面,并会覆盖app.wxss中相同的选择器。
4.内联样式
框架组件上支持使用style和class属性来控制组件的样式,说明如下。

  • style:静态的样式统一写到class中,style接收动态的样式,在运行时会进行解析,尽量不要将静态的样式写进style,以免影响渲染速度,代码示例如下:
<view style="color:{{color}};" />
  • class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上.,样式类名之间用空格分隔,代码示例如下:
<view class="normal_view" />

5.选择器
目前支持的选择器有:
image.png
还有很多支持的选择器,不在这里一一列出,读者可以自行尝试,我们在后面第3章会讲解常用选择器在微信小程序中的使用。

2.4 逻辑层.js脚本

小程序开发框架的逻辑层使用JavaScript引擎,向小程序开发者提供JavaScript代码的运行环境以及微信小程序的特有功能。逻辑层对数据进行处理并发送给视图层,同时接受视图层的事件反馈。开发者写的所有代码最终将打包成一份JavaScript文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似于ServiceWorker,所以逻辑层也称为App Service。
在JavaScript的基础上,为了方便小程序开发增加了以下功能:

  • 增加App和Page方法,进行程序和页面的注册。
  • 增加getApp和getCurrentPages方法,用来获取App实例和当前页面栈。
  • 提供丰富的API,如微信用户数据、扫一扫、支付等微信特有能力。
  • 每个页面有独立的作用域,并提供模块化能力。

小程序框架的逻辑层并非运行在浏览器中,因此JavaScript在Web中的一些能力无法使用,如window、document等。

2.4.1 App方法

小程序的App方法包含一系列函数,例如:App(Object)、onLaunch(Object)、on-Show(Object)、onHide()、onError(String error)、onPageNotFound(Object)、getApp(Object)等。
1. App(Object)
App()函数用来注册一个小程序。接受一个Object参数,指定小程序的生命周期回调等。App()必须在app.js中调用且只能调用一次,否则会出现无法预期的后果。Object参数说明参见表2-7。

表2-7 App()函数的Object参数表

image.png
前台、后台的含义是,当用户点击左上角关闭,或者按了设备Home键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,小程序又会从后台进入前台。需要注意的是,只有当小程序进入后台一定时间,或者系统资源占用过高,小程序才会被真正销毁。
关闭小程序(基础库版本1.1.0开始支持)是指,当用户从扫一扫、转发等入口(场景值为1007, 1008, 1011, 1025)进入小程序,且没有置顶小程序的情况下退出,小程序会被销毁。
小程序运行机制在基础库版本1.4.0有所改变:上面“关闭小程序”逻辑在新版本已不适用。
代码示例如下:

App({
  onLaunch: function(options) {
    // Do something initial when launch.
  },
  onShow: function(options) {
    // Do something when show.
  },
  onHide: function() {
    // Do something when hide.
  },
  onError: function(msg) {
    console.log(msg)
  },
  globalData: 'I am global data'
})

2. onLaunch(Object)
onLaunch()函数在小程序初始化完成时触发,全局只触发一次,其中,Object参数说明参见表2-8。

表2-8 onLaunch()函数的Object参数表

image.png

其中,referrerInfo.appId场景值参见表2-9。

表2-9 referrerInfo.appId场景值

image.png
3. onShow(Object)
小程序启动时或从后台进入前台显示时触发onShow()函数。Object参数说明与on-Launch()函数一致。
4. onHide()
小程序从前台进入后台时触发onHide()函数。
5. onError(String error)
小程序发生脚本错误或者API调用失败时触发onError()函数。参数说明如下:
image.png
6. onPageNotFound(Object)
基础库1.9.90开始支持,低版本需做兼容处理。小程序要打开的页面不存在时触发onPageNotFound()函数。Object参数说明如下:
image.png
开发者可以在onPageNotFound回调中进行重定向,但必须在回调中同步处理,异步处理(例如setTimeout异步执行)无效。代码示例如下:

App({
  onPageNotFound(res) {
    wx.redirectTo({
      url: 'pages/...'
    })    //如果是tabBar页面,请使用wx.switchTab
  }
})
  • 如果开发者没有添加onPageNotFound监听,当跳转页面不存在时,将推入“微信客户端原生的页面不存在”提示页面。
  • 如果onPageNotFound回调中又重定向到另一个不存在的页面,将推入“微信客户端原生的页面不存在”提示页面,并且不再回调onPageNotFound。

7. getApp(Object)
getApp()函数是全局函数,可以用来获取小程序App实例。Object参数说明如下:
image.png
代码示例如下:

// other.js
var appInstance = getApp()
console.log(appInstance.globalData)    // I am global data

不要在定义于App()内的函数中调用getApp(),使用this就可以获取App实例。
通过getApp()获取实例之后,不要私自调用生命周期函数。

2.4.2 运行机制

小程序启动会有两种情况,一种是“冷启动”,一种是“热启动”。假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无须重新启动,只需将后台状态的小程序切换到前台,这个过程就是“热启动”。“冷启动”指的是用户首次打开小程序,或小程序被微信主动销毁后再次打开,此时小程序需要重新加载启动。
小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用。如果需要马上应用最新版本,可以使用wx.getUpdateManager API进行处理。
小程序没有重启的概念。当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁。当短时间内(5分钟)连续收到两次以上系统内存告警,会对小程序进行销毁。
再次打开逻辑:基础库1.4.0开始支持,低版本需做兼容处理。
打开小程序有A和B两类场景。
A场景,打开首页。场景值有以下几项:
image.png
B场景,打开小程序指定的某个页面。场景值为除上面场景以外的其他内容,再次打开一个小程序的逻辑如下:
image.png

2.4.3 场景值

当前支持的场景值参数见表2-10。基础库1.1.0开始支持,低版本需做兼容处理。

表2-10 场景值参数

image.png
image.png
image.png
可以在App的onLaunch和onShow中获取上述场景值,部分场景值下还可以获取来源应用、公众号或小程序的AppID。
由于Android系统限制,目前还无法获取到按Home键退出到桌面,然后从桌面再次进小程序的场景值,对于这种情况,会保留上一次的场景值。

2.4.4 Page方法

小程序的Page方法用于页面的注册和配置。
1.页面Page()函数
Page()函数用来注册一个页面。接受一个Object类型参数,指定页面的初始数据、生命周期回调、事件处理函数等。Object参数说明参见表2-11。

表2-11 Page()函数的Object参数

image.png
image.png
代码示例如下:

//index.js
Page({
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // Do some initialize when page load.
  },
  onReady: function() {
    // Do something when page ready.
  },
  onShow: function() {
    // Do something when page show.
  },
  onHide: function() {
    // Do something when page hide.
  },
  onUnload: function() {
    // Do something when page close.
  },
  onPullDownRefresh: function() {
    // Do something when pull down.
  },
  onReachBottom: function() {
    // Do something when page reach bottom.
  },
  onShareAppMessage: function () {
    // return custom share data when user share.
  },
  onPageScroll: function() {
    // Do something when page scroll
  },
  onResize: function() {
    // Do something when page resize
  },
  onTabItemTap(item) {
    console.log(item.index)
    console.log(item.pagePath)
    console.log(item.text)
  },
  // Event handler.
  viewTap: function() {
    this.setData({
      text: 'Set some data for updating view.'
    }, function() {
      // this is setData callback
    })
  },
  customData: {
    hi: 'MINA'
  }
})

页面可以像自定义组件一样使用Component来创建,这样就可以使用自定义组件的特性。
2.初始数据data
data是页面第一次渲染使用的初始数据。页面加载时,data将会以JSON字符串的形式由逻辑层传至渲染层,因此data中的数据必须可以转成JSON的类型,如字符串、数字、布尔值、对象、数组等。
渲染层可以通过WXML对数据进行绑定。代码示例如下:

<view>{{text}}</view>
<view>{{array[0].msg}}</view>

Page({
  data: {
    text: 'init data',
    array: [{msg: '1'}, {msg: '2'}]
  }
})

3.生命周期回调函数
注册页面时的生命周期回调函数包括onLoad()、onShow()、onReady()、onHide()、on-Unload()。
onLoad(Object query)页面加载时触发。一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数。
参数说明如下:
image.png
onShow()页面显示/切入前台时触发。
onReady()页面初次渲染完成时触发。一个页面只会调用一次,该函数执行完毕代表页面已经准备妥当,可以和视图层进行交互。
对界面内容进行设置的API如wx.setNavigationBarTitle,请在onReady之后进行。
onHide()页面隐藏/切入后台时触发,如navigateTo或底部tab切换到其他页面,小程序切入后台等。
onUnload()页面卸载时触发,如redirectTo或navigateBack到其他页面时。
4.页面事件处理函数
(1)onPullDownRefresh()
监听用户下拉刷新事件。需要在app.json的window选项中或页面配置中开启enable-PullDownRefresh。可以通过wx.startPullDownRefresh触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新。
(2)onReachBottom()
监听用户上拉触底事件。可以在app.json的window选项中或页面配置中设置触发距离onReachBottomDistance。在触发距离内滑动,本事件只会被触发一次。
(3)onPageScroll(Object)
监听用户滑动页面事件。Object参数如下:
image.png
(4)onShareAppMessage(Object)
监听用户点击页面内转发按钮

(<button>组件open-type="share")

或右上角菜单“转发”按钮的行为,并自定义转发内容。
只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮。
Object参数如下:
image.png
此事件需要返回一个Object,用于自定义转发内容,返回内容如下:
image.png
图片路径可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG。显示图片长宽比是5∶4。
代码示例如下:

Page({
  onShareAppMessage: function (res) {
    if (res.from === 'button') {
      //来自页面内转发按钮
      console.log(res.target)
    }
    return {
      title: '自定义转发标题',
      path: '/page/user?id=123'
    }
  }
})

(5)onTabItemTap(Object)
基础库1.9.0开始支持,低版本需做兼容处理。点击tab时触发,Object参数如下:
image.png
代码示例如下:

Page({
  onTabItemTap(item) {
    console.log(item.index)
    console.log(item.pagePath)
    console.log(item.text)
  }
})

5.组件事件处理函数
Page中还可以定义组件事件处理函数。在渲染层的组件中加入事件绑定,当事件被触发时,执行Page中定义的事件处理函数。
代码示例如下:

<view bindtap="viewTap"> click me </view>

Page({
  viewTap: function() {
    console.log('view tap')
  }
})

6. route
Page.route表示到当前页面的路径,类型为String,基础库1.2.0开始支持,低版本需做兼容处理。代码示例如下:

Page({
  onShow: function() {
    console.log(this.route)
  }
})

7. setData
Page.prototype.setData(Object data, Function callback)函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的this.data的值(同步)。参数说明如下:
image.png
Object以key: value的形式表示,将this.data中key对应的值改变成value。
其中key可以用数据路径的形式给出,支持改变数组中的某一项或对象的某个属性,如array[2].message,a.b.c.d,并且不需要在this.data中预先定义。

  • 直接修改this.data而不调用this.setData是无法改变页面的状态的,并会造成数据不一致。
  • 仅支持设置可JSON化的数据。单次设置的数据不能超过1024kb,请尽量避免一次设置过多的数据。请不要把data中任何一项的value设为undefined,否则这一项将不被设置并可能遗留一些潜在问题。

代码示例如下:

<!--index.wxml-->
<view>{{text}}</view>
<button bindtap="changeText"> Change normal data </button>
<view>{{num}}</view>
<button bindtap="changeNum"> Change normal num </button>
<view>{{array[0].text}}</view>
<button bindtap="changeItemInArray"> Change Array data </button>
<view>{{object.text}}</view>
<button bindtap="changeItemInObject"> Change Object data </button>
<view>{{newField.text}}</view>
<button bindtap="addNewField"> Add new data </button>

// index.js
Page({
  data: {
    text: 'init data',
    num: 0,
    array: [{text: 'init data'}],
    object: {
      text: 'init data'
    }
  },
  changeText: function() {
    // this.data.text = 'changed data' //不要直接修改this.data
    //应该使用setData
    this.setData({
      text: 'changed data'
    })
  },
  changeNum: function() {
    //或者,可以修改this.data之后马上用setData设置一下修改了的字段
    this.data.num = 1
    this.setData({
      num: this.data.num
    })
  },
  changeItemInArray: function() {
    //对于对象或数组字段,可以直接修改一个其下的子字段,这样做通常比修改整个对象或数组更好
    this.setData({
      'array[0].text':'changed data'
    })
  },
  changeItemInObject: function(){
    this.setData({
      'object.text': 'changed data'
    });
  },
  addNewField: function() {
    this.setData({
      'newField.text': 'new data'
    })
  }
})

8.生命周期
理解生命周期的含义,将会帮助你理解开发。图2-3为Page实例的生命周期。
image.png

图2-3 Page实例的生命周期

2.4.5 路由

在小程序中,所有页面的路由全部由框架进行管理。框架以栈的形式维护了当前的所有页面。当发生路由切换的时候,页面栈的表现如表2-12所示。

image.png

getCurrentPages()函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。
不要尝试修改页面栈,会导致路由以及页面状态错误。
不要在App.onLaunch的时候调用getCurrentPages(),此时page还没有生成。
路由的触发方式以及页面生命周期函数参见表2-13。

表2-13 路由的触发方式以及页面生命周期函数

image.png
例如,A、B页面为TabBar页面,C是从A页面打开的页面,D页面是从C页面打开的页面,Tab切换对应的生命周期如下:
image.png

  • navigateTo或redirectTo只能打开非tabBar页面。
  • switchTab只能打开tabBar页面。
  • reLaunch可以打开任意页面。
  • 页面底部的tabBar由页面决定,即只要是定义为tabBar的页面,底部都有tabBar。
  • 调用页面路由所带的参数可以在目标页面的onLoad中获取。

2.4.6 模块化

在JavaScript文件中声明的变量和函数只在该文件中有效;不同的文件中可以声明相同名字的变量和函数,不会互相影响。通过全局函数getApp()可以获取全局的应用实例,如果需要全局的数据可以在App()中设置,代码示例如下:

// app.js
App({
  globalData: 1
})

// a.js
// The localValue can only be used in file a.js.
var localValue = 'a'
// Get the app instance.
var app = getApp()
// Get the global data and change it.
app.globalData++

// b.js
// You can redefine localValue in file b.js, without interference with the local-
   Value in a.js.
var localValue = 'b'
// If a.js it run before b.js, now the globalData shoule be 2.
console.log(getApp().globalData)

可以将一些公共的代码抽离成一个单独的.js文件,作为一个模块。模块只有通过module.exports或exports才能对外暴露接口。
exports是module.exports的一个引用,在模块里面随意更改exports的指向会造成未知的错误。所以推荐开发者采用module.exports来暴露模块接口,除非你已经清晰知道这两者的关系。小程序目前不支持直接引入node_modules,开发者需要使用到node_modules时建议拷贝相关的代码到小程序的目录中,或者使用小程序支持的npm功能。
代码示例如下:

// common.js
function sayHello(name) {
  console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
  console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye

在需要使用这些模块的文件中,使用require(path)将公共代码引入,代码示例如下:

var common = require('common.js')
Page({
  helloMINA: function() {
    common.sayHello('MINA')
  },
  goodbyeMINA: function() {
    common.sayGoodbye('MINA')
  }
})

require暂时不支持绝对路径。

2.4.7 API

小程序开发框架提供丰富的微信原生API,可以方便地调用微信提供的能力,如获取用户信息、本地存储、支付功能等。通常,小程序API有以下几种类型:事件监听API,同步API,异步API。
1.事件监听API
以on开头的API为事件监听API,用来监听某个事件是否触发,如wx.onSocketOpen、wx.onCompassChange等。这类API接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。代码示例如下:
wx.onCompassChange(function (res) {
console.log(res.direction)
})
2.同步API
以Sync结尾的API都是同步API,如wx.setStorageSync、wx.getSystemInfoSync等。此外,也有一些其他的同步API,如wx.createWorker、wx.getBackgroundAudioManager等。同步API的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常。代码示例如下:

try {
  wx.setStorageSync('key', 'value')
} catch (e) {
  console.error(e)
}

3.异步API
大多数API都是异步的,如wx.request、wx.login等。这类API接口通常都接受一个Object类型的参数,这个参数用于指定如何接收接口调用结果。
异步API的Object参数说明如下:
image.png
API通常的回调函数有三个:success、fail和complete。回调函数调用时会传入一个Object类型参数,包含以下字段:
image.png
异步API的执行结果需要通过Object类型的参数中传入的对应回调函数获取。部分异步API也会有返回值,可以用来实现更丰富的功能,如wx.request、wx.connectSockets等。代码示例如下:

wx.login({
  success(res) {
    console.log(res.code)
  }
})

2.5 WXML视图层开发

WXML(WeiXin Markup Language)是框架设计的类似于HTML的标签语言,结合基础组件、事件系统就可以构建出页面的结构,组成.wxml文件。WXML中的动态数据均来自对应Page的data。本节主要讲解视图层开发中.wxml文件常用的语法,包含数据绑定、列表渲染、条件渲染、模板、事件、引用等处理。

2.5.1 数据绑定

数据绑定是指在小程序的.js文件里,将data定义的各类数据显示在.wxml页面中。当然data里面定义的各类数据可以通过其他方式进行变更。
1.简单绑定
数据简单绑定是指使用Mustache语法(双大括号)将变量包起来。
.wxml文件代码示例如下:

<view> {{ message }} </view>

.js文件代码示例如下:

Page({
  data: {
    message: 'Hello MINA!'
  }
})

组件属性需要在双引号之内,.wxml文件代码示例如下:

<view id="item-{{id}}"> </view>
.js文件代码示例如下:
Page({
  data: {
    id: 0
  }
})

控制属性需要在双引号之内,.wxml文件代码示例如下:

<view wx:if="{{condition}}"> </view>
.js文件代码示例如下:
Page({
  data: {
    condition: true
  }
})

关键字(需要在双引号之内)包括。

  • true:boolean类型的true,代表真值。
  • false:boolean类型的false,代表假值。

.wxml文件代码示例如下:

<checkbox checked="{{false}}"> </checkbox>

不要直接写checked="false",其计算结果是一个字符串,转成boolean类型后代表真值。
2.运算
可以在{{}}内进行简单的运算,支持如下几种运算:
三元运算,.wxml文件代码示例如下:

<view hidden="{{flag ? true : false}}"> Hidden </view>

算数运算,.wxml文件代码示例如下:

<view> {{a + b}} + {{c}} + d </view>

.js文件代码示例如下:

Page({
  data: {
    a: 1,
    b: 2,
    c: 3
  }
})

view中的内容为3+3+d。
逻辑判断,.wxml文件代码示例如下:

<view wx:if="{{length > 5}}"> </view>

字符串运算,.wxml文件代码示例如下:

<view>{{"hello" + name}}</view>

.js文件代码示例如下:

Page({
  data:{
    name: 'MINA'
  }
})

数据路径运算,.wxml文件代码示例如下:

<view>{{object.key}} {{array[0]}}</view>

.js文件代码示例如下:

Page({
  data: {
    object: {
      key: 'Hello '
    },
    array: ['MINA']
  }
})

3.组合
也可以在Mustache内直接进行组合,构成新的数组或者对象。
(1)数组
.wxml文件代码示例如下:

<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} </view>

.js文件代码示例如下:

Page({
  data: {
    zero: 0
  }
})

最终组合成数组[0, 1, 2, 3, 4]。
(2)对象
.wxml文件代码示例如下:

<template is="objectCombine" data="{{for: a, bar: b}}"></template>

.js文件代码示例如下:

Page({
  data: {
    a: 1,
    b: 2
  }
})

最终组合成对象{for: 1, bar: 2}。
也可以用扩展运算符...将一个对象展开。.wxml文件代码示例如下:

<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template>

.js文件代码示例如下:

Page({
  data: {
    obj1: {
      a: 1,
      b: 2
    },
    obj2: {
      c: 3,
      d: 4
    }
  }
})

最终组合成对象{a: 1, b: 2, c: 3, d: 4, e: 5}。
如果对象的key和value相同,也可以间接地表达。.wxml文件代码示例如下:

<template is="objectCombine" data="{{foo, bar}}"></template>

.js文件代码示例如下:

Page({
  data: {
    foo: 'my-foo',
    bar: 'my-bar'
  }
})

最终组合成对象{foo: 'my-foo', bar:'my-bar'}。
上述方式可以随意组合,但如存在变量名相同的情况,后边的会覆盖前面的,如:

<template is="objectCombine" data="{{...obj1, ...obj2, a, c: 6}}"></template>
Page({
  data: {
    obj1: {
      a: 1,
      b: 2
    },
    obj2: {
      b: 3,
      c: 4
    },
    a: 5
  }
})

最终组合成的对象是{a: 5, b: 3, c: 6}。花括号和引号之间如果有空格,将最终被解析成字符串。
.wxml文件代码示例如下:

<view wx:for="{{[1,2,3]}} ">
  {{item}}
</view>

等同于代码:

<view wx:for="{{[1,2,3] + ' '}}">
  {{item}}
</view>

2.5.2 列表渲染

列表渲染是指,在小程序.js文件里,把data定义的数组数据通过for循环语句显示在.wxml页面中。当然data里面定义的各类数据可以通过其他方式进行变更。在实际的应用中,通过列表渲染可以输出产品列表、新闻列表等。
1. wx:for
在组件上使用wx:for控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
数组的当前项的下标变量名默认为index,数组当前项的变量名默认为item,.wxml文件代码示例如下:

<view wx:for="{{array}}">
  {{index}}: {{item.message}}
</view>
.js文件代码示例如下:
Page({
  data: {
    array: [{
      message: 'foo',
    }, {
      message: 'bar'
    }]
  }
})

使用wx:for-item可以指定数组当前元素的变量名,使用wx:for-index可以指定数组当前下标的变量名,.wxml文件代码示例如下:

<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
  {{idx}}: {{itemName.message}}
</view>

wx:for也可以嵌套,下面是一个九九乘法表的.wxml文件代码示例:

<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i">
  <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j">
    <view wx:if="{{i <= j}}">
      {{i}} * {{j}} = {{i * j}}
    </view>
  </view>
</view>

2. block wx:for
可以将wx:for用在标签上,以渲染一个包含多节点的结构块。.wxml文件代码示例如下:

<block wx:for="{{[1, 2, 3]}}">
  <view> {{index}}: </view>
  <view> {{item}} </view>
</block>

并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
3. wx:key
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如中的输入内容,的选中状态),需要使用wx:key来指定列表中项的唯一标识符。
wx:key的值有以下两种形式。

  • 字符串:代表在for循环的array中item的某个property,该property的值需要是列表中唯一的字符串或数字,且不能动态改变。
  • 保留关键字(*this):代表在for循环中的item本身,这种表示需要item本身是一个唯一的字符串或者数字,例如,当数据改变触发渲染层重新渲染的时候,会校正带有key的组件,框架会将它们重新排序,而不是重新创建,以确保组件保持自身的状态,并且提高列表渲染时的效率。如不提供wx:key,会报错;如果明确知道该列表是静态的,或者不必关注其顺序,可以选择忽略。

.wxml文件代码示例如下:

<switch wx:for="{{objectArray}}" wx:key="unique" style="display: block;"> {{item.id}} </switch>
<button bindtap="switch"> Switch </button>
<button bindtap="addToFront"> Add to the front </button>
<switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch>
<button bindtap="addNumberToFront"> Add to the front </button>

.js文件代码示例如下:

Page({
  data: {
    objectArray: [
      {id: 5, unique: 'unique_5'},
      {id: 4, unique: 'unique_4'},
      {id: 3, unique: 'unique_3'},
      {id: 2, unique: 'unique_2'},
      {id: 1, unique: 'unique_1'},
      {id: 0, unique: 'unique_0'},
    ],
    numberArray: [1, 2, 3, 4]
  },
  switch: function(e) {
    const length = this.data.objectArray.length
    for (let i = 0; i < length; ++i) {
      const x = Math.floor(Math.random() * length)
      const y = Math.floor(Math.random() * length)
      const temp = this.data.objectArray[x]
      this.data.objectArray[x] = this.data.objectArray[y]
      this.data.objectArray[y] = temp
    }
    this.setData({
      objectArray: this.data.objectArray
    })
  },
  addToFront: function(e) {
    const length = this.data.objectArray.length
    this.data.objectArray = [{id: length, unique: 'unique_' + length}].concat (this.data.objectArray)
    this.setData({
      objectArray: this.data.objectArray
    })
  },
  addNumberToFront: function(e){
    this.data.numberArray = [ this.data.numberArray.length + 1 ].concat(this.data.numberArray)
    this.setData({
      numberArray: this.data.numberArray
    })
  }
})

注意,当wx:for的值为字符串时,会将字符串解析成字符串数组,.wxml文件代码示例如下:

<view wx:for="array">
  {{item}}
</view>

等同于:

<view wx:for="{{['a','r','r','a','y']}}">
  {{item}}
</view>

花括号和引号之间如果有空格,将最终被解析成字符串,.wxml文件代码示例如下:

<view wx:for="{{[1,2,3]}} ">
  {{item}}
</view>

等同于:

<view wx:for="{{[1,2,3] + ' '}}" >
  {{item}}
</view>

2.5.3 条件渲染

条件渲染是指,根据if语句中的条件来决定if语句所在区块是显示还是隐藏,如果if条件语句为True,则渲染显示;如果if条件语句为False,则隐藏不做渲染显示。在实际开发中,可以通过按钮来改变if条件语句的变量状态(True和False之间转换)来实现某个区块的显示或隐藏。
1. wx:if
在框架中,使用wx:if="{{condition}}"来判断是否需要渲染该代码块,.wxml文件代码示例如下:

<view wx:if="{{condition}}"> True </view>

也可以用wx:elif和wx:else来添加一个else块,.wxml文件代码示例如下:

<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>

2. block wx:if
因为wx:if是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个标签将多个组件包装起来,并在上边使用wx:if控制属性,.wxml文件代码示例如下:

<block wx:if="{{true}}">
  <view> view1 </view>
  <view> view2 </view>
</block>

3. wx:if vs hidden
因为wx:if之中的模板也可能包含数据绑定,所以当wx:if的条件值切换时,框架有一个局部渲染的过程,以确保条件块在切换时销毁或重新渲染。
同时,wx:if也是惰性的,如果初始渲染条件为False,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。相比之下,hidden就简单多了,组件始终会被渲染,只是简单地控制显示与隐藏。一般来说,wx:if有更高的切换消耗,而hidden有更高的初始渲染消耗。因此,在需要频繁切换的情景下,用hidden更好;而在运行时条件不大可能改变时,则用wx:if较好。

2.5.4 模板

模板用于将一些公用的WXML代码单独整理成一个.wxml文件,然后在有需要的地方直接引入即可。可以在模板中定义代码片段,然后在不同的地方调用。
1.定义模板
使用name属性作为模板的名字,然后在

<template/>

内定义代码片段。.wxml文件代码示例如下:

<!--
  index: int
  msg: string
  time: string
-->
<template name="msgItem">
  <view>
    <text> {{index}}: {{msg}} </text>
    <text> Time: {{time}} </text>
  </view>
</template>

2.使用模板
使用is属性声明需要使用的模板,然后将模板所需要的data传入。.wxml文件代码示例如下:

<template is="msgItem" data="{{...item}}"/>
.js文件代码示例如下:
Page({
  data: {
    item: {
      index: 0,
      msg: 'this is a template',
      time: '2016-09-15'
    }
  }
})

is属性可以使用Mustache语法,动态决定具体需要渲染哪个模板。.wxml文件代码示例如下:

<template name="odd">
  <view> odd </view>
</template>
<template name="even">
  <view> even </view>
</template>
<block wx:for="{{[1, 2, 3, 4, 5]}}">
  <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>

模板拥有自己的作用域,只能使用data传入的数据以及模板定义文件中定义的模块。

2.5.5 事件

事件是控件可以识别的操作,如按下某个按钮,选择某个复选框。每一种控件有自己可以识别的事件,如小程序页面的加载、按钮的单击、表单的提交等事件,也包括编辑框(文本框)的文本改变事件等。
事件有如下特征:

  • 事件是视图层到逻辑层的通信方式。
  • 事件可以将用户的行为反馈到逻辑层进行处理。
  • 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
  • 事件对象可以携带额外信息,如id、dataset、touches。

1.事件的使用方式
在组件中绑定一个事件处理函数,如bindtap,当用户点击该组件的时候,会在该页面对应的Page中找到相应的事件处理函数,.wxml文件代码示例如下:

<view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>

在相应的Page定义中写入相应的事件处理函数,参数是event,.js文件代码示例如下:

Page({
  tapName: function(event) {
    console.log(event)
  }
})

可以看到,log出来的信息大致如下:

{
  "type":"tap",
  "timeStamp":895,
  "target": {
    "id": "tapTest",
    "dataset":  {
      "hi":"WeChat"
    }
  },
  "currentTarget":  {
    "id": "tapTest",
    "dataset": {
      "hi":"WeChat"
    }
  },
  "detail": {
    "x":53,
    "y":14
  },
  "touches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
  }],
  "changedTouches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
  }]
}

2.事件分类
事件分为冒泡事件和非冒泡事件。

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

WXML的冒泡事件参见表2-14。

表2-14 WXML的冒泡事件列表

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

的submit事件,的input事件,的scroll事件。
3.事件绑定和冒泡
事件绑定的写法与组件的属性相关,分为key和value两种形式:
  • key以bind或catch开头,后跟事件的类型,如bindtap、catchtouchstart。自基础库版本1.5.0起,在非原生组件中,bind和catch后可以紧跟一个冒号,其含义不变,如bind:tap、catch:touchstart。
  • value是一个字符串,需要在对应的Page中定义同名的函数,否则当触发事件的时候会报错。

bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
例如,在下面这个例子中,点击inner view会依次调用handleTap3和handleTap2(因为tap事件会冒泡到middle view,而middle view阻止了tap事件冒泡,不再向父节点传递),点击middle view会触发handleTap2,点击outer view会触发handleTap1,.wxml文件代码示例如下:

<view id="outer" bindtap="handleTap1">
 outer view
 <view id="middle" catchtap="handleTap2">
   middle view
   <view id="inner" bindtap="handleTap3">
     inner view
   </view>
 </view>
</view>

4.事件的捕获阶段
自基础库版本1.5.0起,触摸类事件支持捕获阶段。捕获阶段位于冒泡阶段之前,在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字,后者将中断捕获阶段和取消冒泡阶段。
在下面的.wxml文件代码中,点击inner view会依次调用handleTap2、handleTap4、handleTap3、handleTap1:

<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

如果将上面代码中的第一个capture-bind改为capture-catch,将只触发handleTap2。
.wxml文件代码示例如下:

<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

5.事件对象
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。
BaseEvent基础事件对象属性如下:
image.png
其中,type代表事件的类型。
timeStamp页面打开到触发事件所经过的毫秒数。
target触发事件的源组件,属性如下:
image.png
currentTarget事件绑定的当前组件,属性如下:
image.png
说明:target和currentTarget可以参考上例,点击inner view时,handleTap3收到的事件对象target和currentTarget都是inner,而handleTap2收到的事件对象target就是inner,currentTarget就是middle。
CustomEvent自定义事件对象属性(继承BaseEvent)如下:
image.png
TouchEvent触摸事件对象属性(继承BaseEvent)如下:
image.png
image.png
6. dataset
在组件中可以定义数据,这些数据将会通过事件传递给SERVICE。书写方式:以data-开头,多个单词由连字符-链接,不能有大写(大写会自动转成小写),如data-element-type,最终在event.currentTarget.dataset中会将连字符转成驼峰形式,如elementType。
.wxml文件代码示例如下:

<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>

.js文件代码示例如下:

Page({
  bindViewTap:function(event){
    event.currentTarget.dataset.alphaBeta === 1    // -会转为驼峰写法
    event.currentTarget.dataset.alphabeta === 2    //大写会转为小写
  }
})

7. touches
touches是一个数组,每个元素为一个Touch对象(canvas触摸事件中携带的touches是CanvasTouch数组)。表示当前停留在屏幕上的触摸点。
Touch对象属性如下:
image.png
8. changedTouches
changedTouches数据格式同touches,表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)。
9. detail
自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。
点击事件的detail带有的x和y,同pageX和pageY,代表到文档左上角的距离。

2.5.6 引用

WXML提供两种文件引用方式:import和include。
1. import
import可以在该文件中使用目标文件定义的template,例如,在item.wxml中定义了一个名为item的template,.wxml文件代码示例如下:

<!-- item.wxml -->
<template name="item">
  <text>{{text}}</text>
</template>

在index.wxml中引用了item.wxml,就可以使用item模板,.wxml文件代码示例如下:

<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>

import有作用域的概念,即只输入目标文件中定义的模板,而不输入目标文件自己输入的模板。例如,C import B,B import A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template。
.wxml文件代码示例如下:

<!-- A.wxml -->
<template name="A">
  <text> A template </text>
</template>

.wxml文件代码示例如下:

<!-- B.wxml -->
<import src="a.wxml"/>
<template name="B">
  <text> B template </text>
</template>

.wxml文件代码示例如下:

<!-- C.wxml -->
<import src="b.wxml"/>
<template is="A"/>  <!-- Error! Can not use tempalte when not import A. -->
<template is="B"/>

2. include
include可以将目标文件除

<template/> <wxs/>

之外的整个代码引入,相当于拷贝到include位置,.wxml文件代码示例如下:

<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>

.wxml文件代码示例如下:

<!-- header.wxml -->
<view> header </view>

<!-- footer.wxml -->
<view> footer </view>

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: