现学现卖微信小程序开发(一)
现学现卖微信小程序开发(二)
现学现卖微信小程序开发(三):引入Rx,为小程序插上翅膀
现学现卖微信小程序开发
很早就注册了微信小程序内测,因为是拖延症晚期患者,所以一直都没有学习。直到昨天(2016年12月28日),张小龙童鞋宣布微信小程序2017年1月9日正式上线,满屏都是小程序的消息。我这时才想起来,我不能白交了300块钱啊,得学啊。
反正学习也得总结,倒不如现学现卖写一个教程吧。话说回来现学现卖肯定有不少错误,大家多包涵哈。废话少说,我们开工,首先下载微信小程序开发工具 https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html 选择自己的平台下载安装即可。
注册小程序号的过程我就不讲了(网上这类教程一堆,不用我花时间在这里。),你要开发的话,最好通过公司申请一个。第一次启动开发工具时需要扫码,然后选择添加一个无appId的项目。做个什么项目好呢,没想好,那就还是先把Todo牵出来吧。我们来实现一个微信小程序版的Todo,同样是带HTTP后台的版本。为什么又选择Todo呢?因为它的逻辑相对完整,增删改查都有,HTTP请求、返回俱全。在一个平台折腾明白这个app就基本可以上手干活了,这货简直就是新时代的Hello World啊。
文件结构
新建工程时,默认开发工具会生成一个样例给我们。我们就来看看这个样例是什么东东。开发工具的左侧是一个预览,中间那栏是文件目录,看目录结构倒是蛮简单:
全局文件
先来看一下根目录:这个目录下有三个文件app.js,app.json和app.wxss。app.js是应用入口,里面定义了App() 函数,用来注册一个小程序,就像Android里面的Application和Angular2中的。接受一个 object 参数,其指定小程序的生命周期函数等。
//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()本身就是一个函数,在其他页面可以通过系统提供的getApp()来得到应用的实例。
- onLaunch就是一个生命周期函数,是当小程序初始化完成时,会触发这个函数(只触发一次)。这里在应用初始化时向本地存储写入了一个日期。
- globalData是个全局数据的存放区域,示例代码中只放置了一个userInfo作为全局变量。
- 例子代码在这里定义了一个全局方法getUserInfo,这个全局方法的访问可以通过getApp().getUserInfo()。如果你打开
pages/index/index.js
,你会发现示例代码通过getApp()取得应用实例后调用了getUserInfo这个全局方法
//获取应用实例
const app = getApp()
...
//调用应用实例的方法获取全局数据
app.getUserInfo(function(userInfo){
//更新数据
that.setData({
userInfo:userInfo
})
})
那么类似像onLaunch这样的生命周期函数有哪些呢?还有onShow,onHide和onError。onShow和onHide分别是应用从后台切换到前台以及从前台进入后台时调用的。当你关闭小程序或者切换微信应用到别的应用时,微信并没有直接干掉小程序,小程序只是进入后台而已,当你切换回来,小程序就又进入了前台。这个机制其实和Android以及iOS的应用切换很像。onError顾名思义就是小程序发生脚本错误或者api调用失败是触发的。注意:请不要在其他页面调用生命周期函数
接下来我们看看 app.json
,这个东东呢,就是小程序的应用全局配置文件。结构也很简单:pages定义页面的文件路径,window定义小程序整个窗口的一些元素,比如Title的文字,Title的颜色,窗口背景颜色等等。
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "todos",
"navigationBarTextStyle":"black"
},
"debug": true
}
比如在上面的配置下,窗口的展现形式是这样的
如果我们改动app.json
成为下面的样子,那么是什么效果呢?
{
"pages":[
"pages/index/index",
"pages/todos/todos"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#000",
"navigationBarTitleText": "todo",
"navigationBarTextStyle":"white",
"backgroundColor": "#492b2b"
},
"debug": true
}
嗯,导航栏背景变成黑色了,文字白色,一切很完美。且慢,我们不是还添加了一个 "backgroundColor": "#492b2b"
吗,它的效果怎么没有出来呢?你点击头像进入log页面后,再点击左上角返回会看到一个动画,这个动画的背景竟然是我们设置的窗口背景色!?
但为毛在静态界面上看不到呢,我猜想是由于Page挡在了背景前面,但是不是呢,我们来用试验验证一下。怎么验证呢,我们在window的设置中再加一个配置 "enablePullDownRefresh": "true"
,这个是激活下拉刷新的效果的开关,我们试一下,拖住界面往下拉,果然,窗口背景色在后面藏着呢!
同样 backgroundTextStyle
定义的是下拉背景字体、loading 图的样式(仅支持 dark/light)
在这个配置文件中,我们还可以定义用于切换界面的工具栏(tab bar),我们也来试验一下吧。
{
"pages":[
"pages/index/index",
"pages/todos/todos"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#000",
"navigationBarTitleText": "todo",
"navigationBarTextStyle":"white",
"backgroundColor": "#492b2b",
"enablePullDownRefresh": "true"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首页"
}, {
"pagePath": "pages/logs/logs",
"text": "日志"
}]
},
"debug": true
}
嗯嗯,很丑陋,但是毕竟显示出来了。那么问题来了,这个tabBar如何自定义呢?除了list属性,它还提供color,selectedColor,backgroundColor,borderStyle和postion属性。我们都设置一下
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"selectedIconPath": "assets/images/home.png"
}, {
"pagePath": "pages/logs/logs",
"text": "日志",
"iconPath": "assets/images/log.png"
}],
"color": "#b0bec5",
"backgroundColor": "#2196F3",
"selectedColor": "#fff",
"borderStyle": "#F8BBD0",
"position": "bottom"
}
还是比较丑,码农加直男,只能凑活了。你可能发现我在list中一个添加的是selectedIconPath,一个添加的是iconPath。其实我是犯懒,不想再找两个图片而已。恰好首页是默认选中的tab,那么正好两个图标都能显示出来。
除了tabBar,还可以设置一个debug属性,默认是false,如果设置成true,那么你在调试的时候,会发现在console里面显示很多log信息。其信息有Page的注册,页面路由,数据更新,事件触发等。这个开关在开发时建议打开,在发布时记着关闭哈。
再来看 app.wxss
,估计你猜也猜得到,这个就是类似css的样式表了。这个全局样式所有页面都可以使用。
/**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肯定要鼓捣一通,看看到底起什么作用?我们首先加一个背景色,红色比较显眼,background-color: red;
。
首页下方有一片区域没有着色,先不管它,点头像进去看看。
这种变化说明这个container选择器是应用于页面的,根据页面内容来决定高度,所以这个100%并不能填满全屏。好在微信小程序可以接受css的大部分设置,如果把 height: 100%;
改成 height: 100vh;
就可以填满全屏了。
页面文件
页面文件也很简单,基本就是和文件夹同名的 .js
、 .json
、 .wxss
文件,唯一多了一种类型就是 .wxml
文件。.js
文件中定义一个Page函数,用于构建页面视图的逻辑,而.wxml
描述页面视图的结构和布局。首先看一下index.js:
var app = getApp()
Page({
data: {
motto: 'Hello 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
})
})
}
})
由上面的结构可以看出基本上Page函数是一个这样的形式。它接受一个对象,这个对象有一些生命周期函数、事件处理函数和数据对象构成。也就是说上面的代码可以改写成下面这样的形式。
let app = getApp()
let pageParams = {
data: {
motto: 'Hello 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
})
})
}
}
Page(pageParams)
事实上我们更倾向于这么写,因为这样的写法可以使我们避免太多嵌套,以及对pageParams做更多灵活的操作。因为是个对象,我们可以写成 pageParams.data = blablabla
或 pageParams.onLoad = someFunctionCall
。对了,这个对象我也不知道叫什么,自己给它起的名字而已,反正Page函数是需要这么一个参数的。
那么页面的内建函数有哪些呢?可以参照下表,其中前5项属于生命周期函数。
内建函数 | 作用 | 调用机制 |
---|---|---|
onLoad | 监听页面加载 | 生命周期内调用一次 |
onReady | 监听页面初次渲染完成 | 生命周期内调用一次 |
onShow | 监听页面显示 | 每次打开页面都会调用一次 |
onHide | 监听页面隐藏 | 当navigateTo或底部tab切换时调用 |
onUnload | 监听页面卸载 | 当redirectTo或navigateBack的时候调用 |
onPullDownRefresh | 监听用户下拉动作 | 用户动作触发 |
onReachBottom | 页面上拉触底事件的处理函数 | 事件触发 |
onShareAppMessage | 用户点击右上角分享 | 用户点击触发 |
具体可以参看微信官方文档给出的页面生命周期示意图
再来看看 index.wxml
,其实就是类似html的一个模版文件,但是微信自己重造轮子的目的不是很清楚。这里 class="container"
应用了我们的全局样式。
<!--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>
事件绑定是通过在组件标签内加 bindXXX=handler
来完成的,这个 handler
需要在pageParams对象中定义。 {{userInfo.nickName}}
是引用了我们在 index.js
中定义的数据对象的属性。
index.json
也是蛮简单,就是设置页面的一些属性,比如我们改写一下的话:
{
"navigationBarTitleText": "首页"
}
导航栏文字就会变成我们设置的值。
好的,到现在我们应该清楚了大概的微信小应用的基础知识。下一节我们会尝试着做我们的Todo应用了。