- 微信小程序商城开发:界面设计实战
- 黄菊华编著
- 12138字
- 2023-07-19 11:29:41
第2章 小程序基础知识
第1章为小程序入门的各项事宜,接下来本章将介绍微信小程序开发的基础知识:项目有哪些配置文件,微信小程序的各种配置,WXSS样式语言,逻辑层.js脚本,WXML视图层开发等。
2.1 项目配置文件
可以在项目根目录使用project.config.json文件(参见1.3.1节)对项目进行配置,项目配置文件的内容参见表2-1。
表2-1 项目配置文件
其中,compileType的有效值如下。
❏ miniprogram:当前为普通小程序项目。
❏ plugin:当前为小程序插件项目。
setting中可以指定的内容如下:
scripts中指定自定义预处理的命令如下。
❏ beforeCompile:编译前预处理命令。
❏ beforePreview:预览前预处理命令。
❏ beforeUpload:上传前预处理命令。
packOptions用于配置项目在打包过程中的选项。打包是预览、上传时对项目进行的必须步骤。目前可以指定packOptions.ignore字段,忽略配置打包时符合指定规则的文件或文件夹,以跳过打包过程,这些文件或文件夹将不会出现在预览或上传的结果内。
packOptions.ignore为一对象数组,对象元素类型如下:
其中,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配置项列表
(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属性
其中,HexColor为十六进制颜色值,#ffffff表示白色,#000000表示黑色。
注意
navigationStyle只在app.json中生效。开启custom后,低版本客户端需要做好兼容。开发者工具基础库版本切到客户端6.7.2版本开始,navigationStyle: custom对<web-view>组件无效。
app.json示例代码如下,界面示例如图2-1所示:
图2-1 app.json示例界面
{ "window":{ "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "微信接口功能演示", "backgroundColor": "#eeeeee", "backgroundTextStyle": "light" } }
(3)tabBar
tabBar用于设置小程序多tab。例如,客户端窗口的底部或顶部有tab栏可以切换页面,可以通过tabBar配置项指定tab栏的表现,以及tab切换时显示的对应页面。tabBar属性参见表2-4。
表2-4 tabBar属性
其中,list接受一个数组,只能配置最少2个、最多5个tab。tab按数组的顺序排序,每个项都是一个对象,其属性值如下:
当属性iconPath和selectedIconPath的postion为top时,不显示icon。list属性如图2-2所示。
图2-2 list属性示例
(4)networkTimeout
networkTimeout用于指定各类网络请求的超时时间,单位均为毫秒,networkTimeout属性参见表2-5。
表2-5 networkTimeout属性
(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。
表2-6 页面配置属性
配置样例代码如下:
{ "navigationBarBackgroundColor": "#ffffff", "navigationBarTextStyle": "black", "navigationBarTitleText": "微信接口功能演示", "backgroundColor": "#eeeeee", "backgroundTextStyle": "light" }
提示
页面的.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物理像素,不同设备的换算方式如下:
在开发微信小程序时,建议设计师使用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.选择器
目前支持的选择器有:
还有很多支持的选择器,不在这里一一列出,读者可以自行尝试,我们在后面第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参数表
前台、后台的含义是,当用户点击左上角关闭,或者按了设备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参数表
其中,referrerInfo.appId场景值参见表2-9。
表2-9 referrerInfo.appId场景值
3. onShow(Object)
小程序启动时或从后台进入前台显示时触发onShow()函数。Object参数说明与on-Launch()函数一致。
4. onHide()
小程序从前台进入后台时触发onHide()函数。
5. onError(String error)
小程序发生脚本错误或者API调用失败时触发onError()函数。参数说明如下:
6. onPageNotFound(Object)
基础库1.9.90开始支持,低版本需做兼容处理。小程序要打开的页面不存在时触发onPageNotFound()函数。Object参数说明如下:
开发者可以在onPageNotFound回调中进行重定向,但必须在回调中同步处理,异步处理(例如setTimeout异步执行)无效。代码示例如下:
App({ onPageNotFound(res) { wx.redirectTo({ url: 'pages/...' }) //如果是tabBar页面,请使用wx.switchTab } })
注意
● 如果开发者没有添加onPageNotFound监听,当跳转页面不存在时,将推入“微信客户端原生的页面不存在”提示页面。
● 如果onPageNotFound回调中又重定向到另一个不存在的页面,将推入“微信客户端原生的页面不存在”提示页面,并且不再回调onPageNotFound。
7. getApp(Object)
getApp()函数是全局函数,可以用来获取小程序App实例。Object参数说明如下:
代码示例如下:
// 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场景,打开首页。场景值有以下几项:
B场景,打开小程序指定的某个页面。场景值为除上面场景以外的其他内容,再次打开一个小程序的逻辑如下:
2.4.3 场景值
当前支持的场景值参数见表2-10。基础库1.1.0开始支持,低版本需做兼容处理。
表2-10 场景值参数
(续)
(续)
可以在App的onLaunch和onShow中获取上述场景值,部分场景值下还可以获取来源应用、公众号或小程序的AppID。
提示
由于Android系统限制,目前还无法获取到按Home键退出到桌面,然后从桌面再次进小程序的场景值,对于这种情况,会保留上一次的场景值。
2.4.4 Page方法
小程序的Page方法用于页面的注册和配置。
1.页面Page()函数
Page()函数用来注册一个页面。接受一个Object类型参数,指定页面的初始数据、生命周期回调、事件处理函数等。Object参数说明参见表2-11。
表2-11 Page()函数的Object参数
代码示例如下:
//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的参数中获取打开当前页面路径中的参数。
参数说明如下:
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参数如下:
(4)onShareAppMessage(Object)
监听用户点击页面内转发按钮(<button>组件open-type="share")或右上角菜单“转发”按钮的行为,并自定义转发内容。
注意
只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮。
Object参数如下:
此事件需要返回一个Object,用于自定义转发内容,返回内容如下:
图片路径可以是本地文件路径、代码包文件路径或者网络图片路径。支持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参数如下:
代码示例如下:
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的值(同步)。参数说明如下:
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实例的生命周期。
图2-3 Page实例的生命周期
2.4.5 路由
在小程序中,所有页面的路由全部由框架进行管理。框架以栈的形式维护了当前的所有页面。当发生路由切换的时候,页面栈的表现如表2-12所示。
表2-12 当路由切换时页面栈的表现
getCurrentPages()函数用于获取当前页面栈的实例,以数组形式按栈的顺序给出,第一个元素为首页,最后一个元素为当前页面。
注意
● 不要尝试修改页面栈,会导致路由以及页面状态错误。
● 不要在App.onLaunch的时候调用getCurrentPages(),此时page还没有生成。
路由的触发方式以及页面生命周期函数参见表2-13。
表2-13 路由的触发方式以及页面生命周期函数
例如,A、B页面为TabBar页面,C是从A页面打开的页面,D页面是从C页面打开的页面,Tab切换对应的生命周期如下:
提示
● 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参数说明如下:
API通常的回调函数有三个:success、fail和complete。回调函数调用时会传入一个Object类型参数,包含以下字段:
异步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用在<block/>标签上,以渲染一个包含多节点的结构块。.wxml文件代码示例如下:
<block wx:for="{{[1, 2, 3]}}"> <view> {{index}}: </view> <view> {{item}} </view> </block>
注意
<block/>并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
3. wx:key
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如<input/>中的输入内容,<switch/>的选中状态),需要使用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是一个控制属性,需要将它添加到一个标签上。如果要一次性判断多个组件标签,可以使用一个<block/>标签将多个组件包装起来,并在上边使用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传入的数据以及模板定义文件中定义的<wxs />模块。
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的冒泡事件列表
注意
除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如<form/>的submit事件,<input/>的input事件,<scroll-view/>的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基础事件对象属性如下:
其中,type代表事件的类型。
timeStamp页面打开到触发事件所经过的毫秒数。
target触发事件的源组件,属性如下:
currentTarget事件绑定的当前组件,属性如下:
说明:target和currentTarget可以参考上例,点击inner view时,handleTap3收到的事件对象target和currentTarget都是inner,而handleTap2收到的事件对象target就是inner, currentTarget就是middle。
CustomEvent自定义事件对象属性(继承BaseEvent)如下:
TouchEvent触摸事件对象属性(继承BaseEvent)如下:
特殊事件:<canvas/>中的触摸事件不可冒泡,所以没有currentTarget。
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对象属性如下:
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>