前言
离上次写博客又有将近3个月了,一方面是换了新公司实在忙的要命,另一方面是因为自己确实懈怠了不少,有时突然感觉自己想学的东西很多,想重新拾起1、2年前的android开发技能,已经快忘光了,然而目前工作主要是java*的业务开发,也基本不做运维了,担心linux也要和android一样慢慢忘掉了,此时才体会到孔老夫子提出的“温故知新”这四个字的重要性啊,随着年龄的增长,技术必然不能止步不前,一位大学时的友人问我能否接个小程序开发的私活,思来想去,这么久没有认真学一下新的技术,索性接下来边学边做,回首过去,我13年自学开发微信公众号,14年自学开发android,而且均略有小成,所以想借此机会找一找当初的那种学习状态,毕竟我还有一个未完成的全栈梦想呢,哈哈,废话不多说,如标题,从今天开始我就会坚持学习开发一段时间微信小程序,并把这个过程记录下来。
代码构成
从代码构成开始吧,毕竟万物起源于hello world~什么帐号申请、IDE下载之类的我就不在此赘述了,先来看一个最简单的小程序的文件结构,第一次启动IDE会提示可以创建一个简单的QuickStart项目:
如上图,我们就以这个最简单的快速启动模板项目的代码为例,看一看一个小程序项目的基本结构:
如上图,此处重点注意一下标记出的4个文件:
1. wxml
同html很类似,这个文件描述了小程序页面的结构,如上图,我们可以看到view、button、image等标签,这些标签在小程序中被按功能包装了起来,这一点就类似于android开发中的控件概念。还有在上图中我们看到了一些表达式,例如:wx:if=”{{ }}”,第一眼的感觉仿佛是jsp的jstl或者freemarker的动态表达式(c:if等),其实不然,实际上它是数据绑定与条件渲染的结合,那么问题来了,什么是数据绑定,什么又是条件渲染呢?首先来看看数据绑定,QuickStart项目给出的示例代码是这样的:
<!--wxml-->
<view> {{message}} </view>
// page.js
Page({
data: {
message: 'Hello MINA!'
}
})
如上所示,通过 {{ }} 的语法把一个变量绑定到界面上,就叫数据绑定,然后在js中为我们这个变量设置状态(值),如上的例子所示,WXML 中的动态数据均来自对应 Page 的 data,我们把data可以理解成一个固定的json key,包含了所有WXML中的变量定义,如下所示:
data: {
xxx:'',
xxx:'',
...
}
既然说到数据绑定,自然也就不能不说MVVM模式,从传统的MVC到MVP再到MVVM,这种模式在android开发中有较为广泛的应用,还有前端开发中的Vue、React等框架,同时微信小程序也是采用了这种思想。说完了数据绑定,再简单看看条件渲染,通过wx:if即可判断是否需要渲染该代码块,依旧以QuickStart项目中的代码片段为例:
<view class="userinfo">
<button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
如上所示,可以看到在button中有wx:if判断条件,意思就是当条件成立小程序页面才会渲染这个button,否则就会在页面上渲染下面的wx:else中的WXML代码,而wx:else前面有个block标签,也很好理解,由于wx:else里有2个标签(image和text),所以需要用block包起来做为一个整体进行渲染,所以把block理解成块级元素即可,这样我们就算初步理解了数据绑定和条件渲染了,接下来看看样式文件——index.wxss。
2. wxss
wxss文件就相当于html中的css文件,具有 CSS 大部分的特性,而不同的是小程序新增了一个尺寸单位rpx,同android开发中的dp类似都是为了解决不同屏幕大小的设备像素比的问题,在官方文档中是这样定义的:
可以根据屏幕宽度进行自适应,规定屏幕宽为750rpx。
如上所示,也就是说我们可以根据不同设备的宽度的逻辑像素进行换算,例如iphone6的逻辑像素是375px,物理像素是750px,所以1rpx=0.5逻辑像素=1物理像素,小程序中的rpx单位正是以iphone6的屏幕分辨率为基准设计的,因为在iphone6的326ppi像素密度下,1rpx=1物理像素,所以我们在开发时也尽量使用iphone6作为视觉标准,关于移动开发中用到的这些诸如“物理像素”、“逻辑像素”、“dpr”、“dpi”等等不清楚的,可以参考我之前写过的一篇博客(移动开发中“单位”的那些事儿),对这些都做了详细的阐述。关于wxss还有一些常用的基础知识点跟css很类似,例如全局/局部样式、选择器等等,以几种常见的选择器为例:
- .class
.intro
- #id
#firstname
- element
view
如上所示,样式选择器、id选择器、组件选择器,同css完全一样,没有任何区别。关于wxss还有一点了解一下就是全局样式和局部样式,在实际开发中会比较常用:
定义在 app.wxss 中的样式为全局样式,在 page 的 wxss 文件中定义的样式为局部样式。
如上所示,把多个页面通用的样式可以写在app.wxss中进行全局定义,而每个页面个性化的那部分样式写在page下各自的wxss文件中,看完了样式文件,接下来我们看看相对更加重要的js事件交互。
3.js
同样的与web中的javascript类似,js在小程序中也是负责解决页面的动态交互,依旧以QuickStart项目中的示例代码为例:
<view>{{ msg }}</view>
<button bindtap="clickMe">点击我</button>
如上所示,如果我们希望在点击按钮时候做一些事情,那么就需要在js中去写相关的逻辑代码了,在上面的代码中我们看到button组件中声明了这样一个属性bindtap
,这就牵扯到事件的概念了,同时需要注意的是bindtap="clickMe"
称为绑定事件,需要分为两部分看,分别是:
- bindtap
- clickMe
如上所示,bindtap我们称为key,而clickMe称为value,也就是说事件绑定的写法是key/value形式的,首先来看一下key——bindtap,key我们也需要分为两部分看:
- bind
- tap
bind是一个动词,代表绑定动作,而tap是一个名词,代表真正的事件,那么小程序中都有哪些事件呢?随意列举几个先简单看一下:
类型 | 触发条件 |
---|---|
touchstart | 手指触摸动作开始 |
touchmove | 手指触摸后移动 |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 |
tap | 手指触摸后马上离开 |
longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 |
transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 |
所以bindtap共同构成了我们事件绑定的key,这里引用官方文档的一句话:
在小程序中,key 以bind或catch开头,然后跟上事件的类型。
也就是说,我们事件绑定中的key的这个动词,只能是bind或catch,那它们两个又有什么区别呢?官方文档中解释的很清楚:
bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
如上所示,我们先有个大体的印象,至于什么冒泡事件以后再说,本篇blog仅仅作为一个基础入门篇,现在我们已经清楚了bindtap表示绑定了一个触摸事件,那接下来看一下key/value中的value。在上面的代码中value的值是clickMe,这相当于一个函数名,类似于我们在html中写的onclick函数:
<button id="btn1" name="btn" onclick="clickMe();"/>
所以说value我们仅需要在js中对应的去定义好即可,至于怎么定义,官方文档中也给出了明确的说明:
在相应的Page定义中写上相应的事件处理函数,参数是event。
同时也给出了示例代码:
Page({
tapName: function(event) {
console.log(event)
}
})
如上所示,我们的事件绑定函数需要定义在Page中,tapName就是对应的函数名,同时我们发现事件处理函数还有一个参数event,下面通过运行程序点击button看一下event里都有什么数据:
如上图,点击button后我们在控制台可以看到一组json格式的数据,这就是该事件所包含的一些相关信息,例如事件类型(tap)、时间戳和控件位置坐标等等,在本篇blog中暂不做过多说明,我们大致了解一下即可。现在我们已经理解了关于事件绑定的相关使用方法,那么再回头看一下上图中的代码,细心的朋友可能会注意一下第10行:
canIUse: wx.canIUse('button.open-type.getUserInfo')
如上所示,将wx.canIUse这个东东的返回值赋值给了canIUse变量,那么这行代码是什么意思呢?这就要提一下小程序给我们提供的API了,这是小程序非常nice的一点,通过这些API,我们可以方便的调用微信小程序为我们提供的能力,例如获取用户信息、本地存储、微信支付等,而不用再像android开发那样去集成对接第三方SDK再去写繁琐的实现代码等等,所以说小程序API很棒很nice,可以让我们直接调用API快速实现一些功能性问题,从而把精力集中在产品自身的业务逻辑上。下面简单看一下官方文档中对于小程序API的通用说明:
1.wx.on 开头的 API 是监听某个事件发生的API接口,接受一个 CALLBACK 函数作为参数。当该事件触发时,会调用 CALLBACK 函数。
2.如未特殊约定,其他 API 接口都接受一个OBJECT作为参数。
3.OBJECT中可以指定success, fail, complete来接收接口调用结果。
简单了解一下,并注意下第2点,也就是说一般的API接口都会接受一个OBJECT做为参数,就如同我们上面简易教程的代码wx.canIUse('button.open-type.getUserInfo')
,接下来我们具体看一下wx.canIUse(String)这个API的使用方法,官方文档中做了详细的说明:
判断小程序的API,回调,参数,组件等是否在当前版本可用。
如上所示,这个API主要是做一些组件在当前版本的可用性check,接下来我们看一下参数说明,也就是button.open-type.getUserInfo
代表什么意思,在官方文档中给出了两种参数形式,分别是:
- api.method.param.options
- component.attribute.option
如上所示,可以理解成有两个重载方法,第一种传4个参数,第二种传3个参数,我们上面的例子显然是第二种方式,再来看一下这些参数名的含义:
- api 代表 API 名字
- method 代表调用方式,有效值为return, success, object,callback
- param 代表参数或者返回值
- options 代表参数的可选值
- component 代表组件名字
- attribute 代表组件属性
- option 代表组件属性的可选值
现在就清楚了,我们例子中的参数'button.open-type.getUserInfo'
表示open-type属性为getUserInfo的button组件。大致了解了js之后,我们最后看一下app.json这个文件。
4. app.json
先来直接看一下官方文档中对app.json的概述:
app.json 是对当前小程序的全局配置,包括了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等。
如上所示,暂且理解为一个全局配置,有点类似于android中的manifest文件,下面看一下QuickStart项目中的app.json文件的具体代码:
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
如上所示,只有短短12行的json格式文件,可以看到有2个key,分别是pages和window,下面就具体看一下这两项配置的作用,引用官方文档的原话:
pages字段 —— 用于描述当前小程序所有页面路径,这是为了让微信客户端知道当前你的小程序页面定义在哪个目录。
如上所示,pages对应一个字符串数组,文档说它们描述了小程序所有的页面路径,换句话说,就是小程序中的所有页面都需要在app.json文件中声明具体的路径,注意理解我这里用的声明(declare)这个词,也就是说如果创建了某个wxml文件却没有在app.json中做声明配置,那么就意味着小程序找不到这个页面,当然也就无法正常显示了。下面先看一下我们demo项目中的页面文件目录:
如上图,可以看到pages目录下有两个目录,分别是index和logs,每个目录下放的是页面样式等相关文件,目录名与文件名相同,这里注意我们在app.json定义的第一个路径是pages/index/index
,但是我们在pages/index
目录下有3个文件,只在配置中写文件的前缀不写后缀就可以自动匹配所有文件吗?答案是肯定的,文档中也说明了这一点:
文件名不需要写文件后缀,因为框架会自动去寻找路径下 .json, .js, .wxml, .wxss 四个文件进行整合。
如上所示,这样我们就清楚了在app.json中可以不用配置文件后缀,当然前提是文件前缀必须一致,这一点很重要。最后还有一个细节性的点,就说pages配置的这个数组的第一项代表小程序的初始页面,上面我们配置的第一项是index,所以小程序的启动页就是index.wxml了,关于pages先说这么多,接下来看看window这个配置项,继续看官方文档中对于window这个配置项的概述:
window字段 —— 小程序所有页面的顶部背景颜色,文字颜色定义在这里的。
如上所示,说白了就是一些状态栏、导航条的样式设置等,也不多总共就7个配置项,包括状态栏、导航条、标题、窗口背景色,在说配置之前我们首先需要明白哪里是状态栏、导航条等等,下面具体看一下模拟器中的界面:
如上图,标记出的这一块就是导航条(navigationBar),所以我们在QuickStart项目中配置的下面这3项都是关于navigationBar的:
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
如上所示,依次看一下,第一个顾名思义就是导航条的背景色了,这里给的是白色(#fff),注意下仅支持HexColor(十六进制颜色值,例如:#fff000),不像css中还可以写成单词形式(red、white等等)。下面我们随意修改一下就可以更清楚的看出navigationBar的范围了:
如上图,第二个配置navigationBarTitleText代表导航条标题,也就是上图中的WeChat字样,当然配置在app.json中是全局的标题,如果我们自定义的页面需要个性化的标题也很简单,直接在该页面的目录下创建自己的xxx.json进行配置即可,优先级自然就不用说了。第三个配置navigationBarTextStyle**代表导航栏标题颜色,默认白色(white),并且仅支持白色和黑色( black/white)**这两种颜色,下面分别看一下这两种样式的效果图:
上面详细解析了window配置项中的3个,那么下面就把剩下的4个也依次了解一下:
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
backgroundColor | HexColor | ffffff | 窗口的背景色 |
backgroundTextStyle | String | dark | 下拉背景字体、loading 图的样式,仅支持 dark/light |
enablePullDownRefresh | Boolean | false | 是否开启下拉刷新 |
onReachBottomDistance | Number | 50 | 页面上拉触底事件触发时距页面底部距离,单位为px |
如上所示,首先来看backgroundColor——窗口的背景色,那窗口指的是哪里呢?看一下官方文档中的这张图:
如上图,background位于navigationBar和page之间,但是我们在模拟器中并不能看到,原因是它需要下拉才能显示这片区域,所以我们首先需要把上面4个配置项中的enablePullDownRefresh属性设置为true来开启下拉刷新。开启之后设置一个背景颜色(#ffff00),然后在模拟器中来尝试一下下拉刷新动作:
如上图,这就是小程序自带的下拉刷新,我们可以清楚的看到background区域,接下来看一下backgroundTextStyle属性,默认为light且仅支持light/dark这两个属性值,默认的效果在上面的动图中已经看过了,那我们修改为dark再看一下下拉的样式:
如上图,很明显下拉刷新的loading样式发生了改变。下面接着看剩下的最后一个window配置项——onReachBottomDistance,官方文档的解释是:
页面上拉触底事件触发时距页面底部距离,单位为px,默认值为50。
如上所示,这个属性提到了上拉触底(onReachBottom),这个操作类似于android中的上拉加载的概念,通常用于分页数据的加载,在此我们暂不做过多介绍,先了解有这么个东西就行,以后在实际应用中再做详细说明,到此为止关于app.json中的window的所有属性配置项就全部介绍完毕了,同时我们的第一个QuickStart项目的代码中最重要的4个文件(wxml、wxss、js、app.json)也算是全部介绍完毕了。
总结
本篇blog以QuickStart项目为例,整体介绍了微信小程序的项目结构以及4个最重要的文件类型,并适当与android开发做了比较,算是对小程序做了一个入门性的认识以及建立一个大体的概念模型,在后续的blog中会逐步深入去研究小程序的技术点,直到最终能写出一个漂亮完整的小程序!周末愉快,The End。