关注小编微信公众号公众号【前端基础教程从0开始】回复“1”,拉你进程序员技术讨论群,群内有大神,可以免费提供问题解答。公众号回复“小程序”,领取300个优秀的小程序开源代码+一套入门教程。公众号回复“领取资源”,领取300G前端,Java,产品经理,微信小程序,Python等资源合集大放送,让我们一起学前端。
微信小程序自九月份推出内测资格以来,经历了舆论热潮到现在看似冷清,但并不意味着大家不那么关注或者不关注了。我想不管是否有内测资格,只要是感兴趣的开发者已经进入潜心耕耘产品的阶段了,至少是静下心来思考与学习。如果对微信小程序的前途和定位有什么疑惑,可以看看这篇文章。
下面从字母a开始编号的内容主要作为后续学习与思考后对文章做的更新,包括项目的改进、观点的补充等,所以建议大家先将整篇文章看完再来浏览这部分,会有不一样的收获。
a. 3.1.2节index.js实现火车票查询事件的响应函数getTrainInfo时,直接在函数体中调用wx.request方法开始网络请求。但是后面在阅读了别人的代码后发现,可以将常用的url、apikey、request等常量和方法抽象出来,定义到独立的文件当中。于是在根目录下新建requests目录,其中新建文件request与api文件(js格式),api用来定义网路请求所需的url、apikey等常量信息,request用来定义网络请求方法(可以多种形式)。这样在不同页面中请求不同网络数据时,只要传不同的参数即可,调用的方法是共用的,提高了可读性与维护性。这部分改变已经同步更新到了Github。
b. 3.1.2和3.2.2节都提到了,将JSON格式数据作为url参数传递前,需要先转化为String类型,然后目标页面读取数据时需要将String类型对象转回到JSON格式。如果不想这么复杂,而又只是以学习为目的的话,可以借助app.js中定义的全局变量来承载数据,因为项目的任何地方都可以获取这种变量值。
app中添加相关变量trainList:
1 globalData:{
2 userInfo:null,
3 trainList:[] //火车票信息列表
4 }
index页面获取网络请求返回数据后直接对全局变量赋值:
1 app.globalData.trainList = res.data.data.trainList;
train页面获取:
1 this.setData({
2 trainList: app.globalData.trainList,
3 });
这样就避免了转化的步骤,但是如果是正规大型项目,还是建议采用最合适的处理方式,毕竟全局变量是针对整个项目的。
2. 界面展示
结合动态图描述一下目前实现的功能:
2.1 主页上半部分显示用户头像与用户名(和微信中的信息一致,这部分组件是工具自带的,我们可以修改这部分组件和内容,稍候会提到);下半部分显示一个经典的问候语“Hello World”,提供一个可点击的按钮“点击获取火车票”;
2.2 点击按钮后,通过事先指定的参数(调用了百度APIStore中去哪网火车票查询接口,站-站查询所需参数为始发地、目的地及时间)发送网络请求,将获取到的JSON数据按火车车次为节点进行解析并在新页面显示基本信息(除了详细座位信息),为每个车次提供一个可点击的按钮“点击查看座位信息”;
2.3 点击某车次中的座位查询按钮后,会将该车次对应的所有座位信息显示在新页面中;
2.4 点击后两个页面左上角的“返回”按钮可回到上一页,这个功能也是工具自带的;
顺便提一下在博客园中插入动画,上面的演示过程是一张格式为gif的图片,像添加普通图片一样操作即可。录制工具使用的是灵者Gif录制,可以指定开始、停止时所需操作与区域等录制信息。
3. 要点分析
关于微信小程序工具的使用及初始项目的结构说明,网上资源已经很丰富,这里不打算再啰嗦,有不了解的可以参考这里。
下面开始讲讲我个人在学习与开发过程中认为值得分享与记录的点,欢迎大小神们一起讨论与指正,特别是讲得不对或有待改善的地方。下面只给出和讲的点直接相关的代码,整体代码可以到项目工程中去查看,建议大家自己调试一遍。
3.1 index
index是项目新建时自动生成的,作为小程序的启动页面。
3.1.1 index.wxml
首页的头像与用户名,从上图的演示过程可以看出我将微信的名字“***”改成了“用户名”:
1 <view bindtap="bindViewTap" class="userinfo">
2 <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
3 <text class="userinfo-nickname">用户名</text> <!-- {{userInfo.nickName}}直接写成“用户名” -->
4 </view>
用户名部分原本的内容为{{userInfo.nickName}},{{key_name}}的作用是获取关键字名为key_name对应的值(数据一般以key_name:value的形式定义在wxml文件同目录下js文件的data成员中,后面会讲解),image显示的头像资源也是通过这种方式指定为src="{{userInfo.avatarUrl}}",程序中产生的数据可以在开发者工具顶部偏右的AppData栏中查看。
如果不需要从js文件中获取数据,那么可以像代码中“用户名”那样直接写入数据值,不过一般不推荐这样做,因为像Android等平台App在开发时会将数据值放入strings.xml等文件,目的是为了将数据与布局分离,布局和功能实现代码分离,方便开发与维护。
组件中的class项用来设置其样式,属性名对应的样式信息定义在wxss文件中,除了可以使用定义在本目录wxss文件中的样式,还可以使用app.wxss文件中定义的。如果样式只是在某页面中使用,那么建议定义在其目录下的wxss文件中,即局部作用域内;如果是多个页面共同使用,即全局样式,那么一般定义在主程序app.wxss文件中。class样式可以指定组件的宽高、背景颜色等属性,本文不再进行详述。
在界面下方添加按钮“点击获取火车票”组件:
1 <view class="gettrain-button" bindtap="getTrainInfo">
2 <text>点击获取火车票</text>
3 </view>
按钮的目标是为了让用户可以点击进行交互,至于使用button、text或其他组件,视具体需求而定。这里是利用text组件,文本内容直接写入了字串“点击获取火车票”,对于只有一个子组件的布局其实可以如下面代码不用嵌套,一层布局搞定。一般来说嵌套层数越少,加载速度越快,这对移动程序的体验是至关重要的。
1 <view class="gettrain-button" bindtap="getTrainInfo">
2 点击获取火车票
3 </view>
但如果在父容器下有多个子组件共享其定义的样式,那么嵌套可以另代码简洁很多:
1 <view class="gettrain-button">
2 <text bindtap="getTrainInfo">点击获取火车票</text>
3 <text bindtap="getCarInfo">点击获取汽车票</text>
4 <text bindtap="getPlaneInfo">点击获取飞机票</text>
5 </view>
组件若要有点击交互功能,须为其绑定事件响应方法,常用的有单点–bindtap,长按–binglongtap。bindtap=“getTrainInfo”,双引号中的文本是方法名称,在js文件中以该名定义方法,做需要的处理即可。
3.1.2 index.js
实现wxml布局中按钮“点击获取火车票”绑定的函数功能:
1 //获取火车票函数
2 getTrainInfo: function() {
3 wx.request({
4 url: 'http://apis.baidu.com/qunar/qunar_train_service/s2ssearch',
5 header: {
6 apikey: '361cf2a2459552575b0e86e0f62302bc',
7 },
8 data: {
9 version: '1.0',
10 from: '北京',
11 to: '杭州',
12 date: '2016-11-15',
13 },
14 success: function(res) {
15 var json = res.data;
16 //将JSON类型转为String类型用以url参数传递,否则传递后会变成[object Object]
17 var jsonString = JSON.stringify(json);
18 wx.navigateTo({
19 url: '../train/train?trainInfos='+jsonString,
20 });
21 },
22 });
23 },
我们先来看看微信小程序官网对于网络请求方法–wx.request(OBJECT)的说明:
一般来说,wx api提供的方法默认会有一个Object参数,需要时传入,不需要时不传便是。不过这对于像我这种Android开发者来说一开始有点不适应,怎么函数调用时都传入一个{…}参数,内部各个项之间用逗号“,”分隔,代码中的url、data等。
从代码中看,发起网络请求时传入了图中列出的四项参数:url、header、data及success,不同需求传入的参数也会不同。对于wx.request方法而言,需根据网络请求目标来传参数的是前四项:url、header、data及method。
以本案例利用百度APIStore去哪网火车票获取站–站火车票信息来说,其官网给出的接口调用的参数信息与格式如下:
将上面两张图中的信息结合起来看,参数是一一对应的:
wx url——火车票查询 接口地址;
header——请求参数header;
data——请求参数urlParam;
method——请求方法;
因为wx中的method参数默认是GET,和火车票查询接口指定的一致,所以调用时可以省略。
而对于最后三个回调函数:success、fail及comlete,代码中添加了success,在请求成功时对数据进行处理。当然,一般的程序还得对请求失败的情况做处理。下面就来分析success方法中的代码,包括JSON数据的转换与新页面的跳转,请求返回的数据以参数res的形式传入到function中。先来看看res中包含了哪些信息,通过代码console.log(res)可以将其打印在工具调试页面的Console项中。
request–ok和statusCode–200表示请求成功,所以才会回调success方法。而data对象才是我们需要的数据,更精确地说,data.data.trainList对象才是真正的火车票信息。
var json = res.data,获取data对象(网络请求返回的数据一般为JSON格式),赋给变量json;
var jsonString = JSON.stringify(json),将JSON类型对象暂时转换为String类型,用来作为url的参数部分进行传递;一开始在这里耽搁了很久,不进行转换直接传的话在目标页面获取不到想要的数据,下面会说明原因;
url: ‘…/train/train?trainInfos=’+jsonString,通过url指定的信息跳转到对应页面,如果不需要额外参数,直接写url: ‘…/train/train’;如果只是传递简单的值,可写成url: ‘…/train/train?param=123’;
至此,如果网络没有问题,点击按钮便可以进行火车票的查询并携带结果数据跳转到新页面了。
3.2 train
train是自定义新建的页面,用来显示火车票基本信息,注意新添的页面需在app.json文件中进行配置。
1 “pages/train/train”, //火车票车次信息页面
2 “pages/seat/seat” //车次余票信息页面
3.2.1 train.wxml
由于站–站火车票所有车次的始发站和终点站是一样,如北京–杭州东,所以先在页面顶部显示站点信息:
1 <text class="train-item">出发地:{{trainList[0].from}}</text>
2 <text class="train-item">目的地:{{trainList[0].to}}</text>
trainList对象会在js文件中定义成data成员,值为上面最后一张图中的JSON对象–trainList,即火车票车次数组,每个元素包含一个车次的具体信息。
接下来显示每个车次的信息,以横线作分隔(由于是以学习和测试为目的,所以就没有在布局的美观上下功夫,大家见谅):
1 <view class="line"></view>
2 <block wx:for="{{trainList}}" wx:for-item="train">
3 <text class="train-item">{{index+1}}. 车次:{{train.trainNo}}</text>
4 <text class="train-item">车型:{{train.trainType}}</text>
5 <text class="train-item">起始时间:{{train.startTime}}</text>
6 <text class="train-item">到站时间:{{train.endTime}}</text>
7 <text class="train-item">总时长:{{train.duration}}</text>
8 <view id="trainindex-{{index}}" class="getseat-button" bindtap="getSeatInfo">
9 <text>点击查看座位信息</text>
10 </view>
11 <view class="line"></view>
12 </block>
第1、11行很简单,在站点与车次、车次与车次之间添加横线。
当布局中的组件个数和js中的数据有关,即在wxml中写死组件不能满足需求时,可以利用block和wx:for来进行组件的动态生成。
第2行wx:for="{{trainList}}"表示block块中的组件可以使用数组trainList中的内容,从下标0开始迭代,数据中有几个元素,就会动态生成几套组件。wx:for-item="train"指定数组中元素的名称为train(默认的是item,指定的意义之一是可读性强),后续获取属性值时可通过train.key_name的形式。
第3行开始添加组件,类型是text,值为{{index+1}}. 车次:{{train.trainNo}},前半部分用来标明每个车次的序号,从1开始;而index和item类似,是默认的迭代索引名称,其实就是数组元素当前的下标,从0开始。
后面几行添加text组件和第3行差不多,但第8行有两个点说一下:
*1 bindtap=“getSeatInfo”,绑定一个回调函数,点击时跳转到新页面,显示当前车次对应的座位信息;
*2 id=“trainindex-{{index}}”,给组件指定id,可以看到之前的组件都没有设置过该属性(不需要就可以不设置),那么什么时候需要呢?其中一种情况,当js中某组件绑定的回调方法需要得知是哪个组件触发了自己的时候,比如第一点中的方法getSeatInfo,要想点击某车次的查看座位信息按钮后显示出对应的座位信息,就得知道点击组件对应的trainList数组下标,而这个需求,正好可以借助id和index属性来实现;
3.2.2 train.js
首先定义data成员trainList,用来接收index页面传递过来的数据:
trainList: []
页面启动时若有数据需要载入,那么得添加onLoad方法(一开始自动运行,在其中实现数据的加载与处理),否则可以不添加。
1 onLoad: function(options) {
2 var jsonString = options.trainInfos;
3 //将字串类型转为JSON类型
4 var json = JSON.parse(jsonString);
5 this.setData({
6 trainList: json.data.trainList,
7 });
8 },
当方法的调用者有参数传入时,我们可以通过添加方法参数的形式来获取。对于参数名,自动启方法一般为options,组件回调方法一般为e(event)。
第2行获取index页面在打开train页面时传入的火车票信息参数trainInfos。
第4行将String类型对象转换回JSON格式,之前在index页面提到过,url传的参数是由JSON格式对象转换过来的String类型。
第6行将真正的火车票车次信息数组取出,赋给数据成员trainList。
注意:给数据成员赋值时,必须调用页面自身的setData方法,否则就算赋值了也不会同步到wxml文件中去,这一点容易出错且不好定位原因。
车次数组得到后,wxml文件就会根据组件的属性设置显示对应的信息。再来看实现按钮“点击查看座位信息”对应的回调方法:
1 getSeatInfo: function(e) {
2 var prefix = 'trainindex-';
3 var trainIndex = e.currentTarget.id.substring(prefix.length);
4 //输出根据组件id获取的车票索引,用以显示详细的座位信息
5 console.log(trainIndex);
6 var trainNo = this.data.trainList[trainIndex].trainNo;
7 var json = this.data.trainList[trainIndex].seatInfos;
8 //将JSON类型转为String类型用以url参数传递,否则传递后会变成[object Object],同时传递车次
9 var jsonString = JSON.stringify(json);
10 wx.navigateTo({
11 url: '../seat/seat?'+'trainNo='+trainNo+'&seatInfos='+jsonString,
12 });
13 },
第2、3行获取之前定义的组件id中的index部分,即点击组件对应的trainList数组的下标。当然原先定义时也可以不添加前缀’trainindex-’,完全是为了可读性,因为当项目越来越大时有个一目了然的标示总是不错的。
第6、7行分别获取车次信息的列车号与座位信息,他们稍候会被传递到seat页面。
第9行同样地将得到的JSON格式对象先转换为String类型,让其可以在url中作为可被正确传递的参数。
第11行打开新的页面seat显示座位信息,多个参数之间以“&”符号分隔。
3.2.3 train.json
主程序中app.json文件除了配置需要调用onLoad方法的页面外,还指定了一些全局的window样式。若某个页面在自己的json文件中没有定义局部的window属性,或根本没有json文件,那默认将使用全局的。
项目初始没有为index生成json文件,因为其作为启动页,直接用全局的“WeChat”就好,其实index标题应该是小程序的名称,我们自己真正开发的程序肯定得取另一个名字。
可以看到,logs、train及seat都对标题进行了定义,结果就是会覆盖掉全局的值。以train为例,其在json文件中定义标题为“站-站火车查询信息”:
1 {
2 "navigationBarTitleText": "站-站火车查询信息"
3 }
还有一点,页面的json文件不需要也不能页面配置属性(Pages),只能设置window属性,所以就可以省略window名称,直接像上述代码用{…}形式即可。
3.3 seat
seat页面用来显示某车次的座位信息,包括座位等级、票价及余票。通过train页面的分析,相信大家对网络请求,数据在页面与页面、wxml与js文件之间的传递渐渐熟悉了。而seat和train类似,没有什么特别的地方,所以和logs一样这里就不再讲什么了。