# 逻辑层
# 1. 逻辑层介绍
小程序开发框架的逻辑层使用JavaScript
引擎,为小程序提供开发者JavaScript
代码的运行环境以及 FinClip 小程序的特有功能。
逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
开发者写的所有代码最终将会打包成一份JavaScript
文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似ServiceWorker
,所以逻辑层也称之为App Service
。
在 JavaScript
的基础上,我们增加了一些功能,以方便小程序的开发:
- 增加
App
和Page
方法,进行程序注册和页面注册; - 增加
getApp
和getCurrentPages
方法,分别用来获取 App 实例和当前页面栈; - 提供
模块化
能力,每个页面有独立的作用域。
# 2. 注册小程序
# App(Object)
App() 函数用来注册一个小程序。接受一个 Object 参数,
App() 必须在 app.js 中调用,且只能调用一次。
Object参数说明
属性 | 类型 | 描述 | 触发时机 |
---|---|---|---|
onLaunch | Function | 生命周期回调—监听小程序初始化 | 小程序初始化完成时触发(全局只触发一次) |
onShow | Function | 生命周期回调—监听小程序显示 | 小程序启动,或从后台进入前台显示时触发 |
onHide | Function | 生命周期回调—监听小程序隐藏 | 小程序从前台进入后台时触发 |
onError | Function | 错误监听函数 | 当小程序发生脚本错误,或者 api 调用失败时触发,会带上错误信息 |
onPageNotFound | Function | 页面不存在监听函数 | 小程序要打开的页面不存在时触发,会带上页面信息回调该函数 |
其他 | 不限制 | 开发者可自由添加任意的 function 或数据到 Object 参数中,用this 可访问 |
前台、后台定义
小程序启动后,用户能够看到当前界面,此时小程序处于前台状态, 当用户通过右上角椭圆按钮关闭小程序或者离开宿主app时,小程序并没有立刻终止运行,而是进入了后台状态,此时会触发 onHide 回调事件。
当用户再次进入宿主app或再次打开小程序,小程序又会从后台切换至前台,此时会触发onShow 回调事件; 如果用户长时间没有打开小程序,或者系统资源紧张,小程序可能被销毁,此时小程序会完全退出。
示例代码
App({
onLaunch: function(options) {
console.log("launch 参数",options)
},
onShow: function(options) {
console.log("onShow 参数",options)
},
onHide: function() {
},
onError: function(error) {
console.log("错误信息:",error)
},
globalData: ''
})
# onLaunch(Object)
小程序初始化完成时触发,全局仅触发一次。
Object 参数说明
字段 | 类型 | 说明 |
---|---|---|
path | String | 打开小程序的路径 |
query | Object | 打开小程序的 query 字段,可通过分享或者唤起协议中配置 |
referrerInfo | Object | 由另一个小程序或其他 App 进入小程序时,返回此字段 |
referrerInfo.appId | String | 来源小程序的 appId,详见下方说明 |
referrerInfo.extraData | Object 其他来源传过来的数据 |
# onShow(Object)
小程序启动,或从后台进入前台时触发,每次切换到前台均会触发。
Object 参数说明
与 onLaunch 一致
# onHide()
小程序从前台进入后台时触发,每次切换到后台均会触发
# onError(String error)
小程序发生脚本错误,或者 api 调用失败时触发。
参数说明
名称 | 类型 | 说明 |
---|---|---|
error | String | 包含堆栈的错误信息 |
# onPageNotFound(Object)
要打开的目标页面不存在时触发,经常用于捕获路由跳转的目标页面不存在的情况。
参数说明
名称 | 类型 | 说明 |
---|---|---|
path | String | 不存在的页面的路径 |
query | Object | 打开不存在得页面的 query 参数 |
isEntryPage | Boolean | 是否本次启动的首个页面(例如从分享等入口进来,首个页面是开发者配置的分享页面) |
开发者可以在 onPageNotFound 回调中进行重定向处理,但必须在回调中同步处理,异步处理无效。
示例代码
App({
onPageNotFound(res) {
jd.redirectTo({
url: 'pages/index/index.fxml'
})
}
})
注意
- 如果开发者没有添加 onPageNotFound 监听,当跳转的目标页面不存在时,将由宿主APP接管处理;
- 请确保 onPageNotFound 回调中重定的目标页面存在,否则将由宿主APP接管处理,并且不再回调 onPageNotFound,避免调用死循环。
# getApp(Object)
全局方法,getApp() 函数可以用来获取到小程序 App 实例,多用于页面中调用,获取APP实例的全局数据和方法。值得注意的是,在app.js中的 APP()方法中调用时,可通过 this 直接获取到,在其他页面中用 getApp() 方法。
Object 参数说明
字段 | 类型 | 说明 |
---|---|---|
allowDefault | Boolean | 在 App 未定义时返回默认实现。当App被调用时,默认实现中定义的属性会被覆盖合并到 App 中。 |
示例代码
const APP = getApp();
console.log(APP.globalData) // 输出 global data
# 3. 注册页面
# 3.1 使用 Page 构造器注册页面
Page(Object) 函数用来注册一个页面。接受一个 Object 类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
属性 | 类型 | 描述 |
---|---|---|
data | Object | 页面的初始数据 |
onLoad | Function | 生命周期回调—页面加载时触发 |
onShow | Function | 生命周期回调—监听页面显示 |
onReady | Function | 生命周期回调—监听页面初次渲染完成 |
onHide | Function | 生命周期回调—监听页面隐藏 |
onUnload | Function | 生命周期回调—监听页面卸载 |
onPullDownRefresh | Function | 触发下拉刷新时执行 |
onReachBottom | Function | 页面触底时执行 |
onShareAppMessage | Function | 转发 |
onPageScroll | Function | 页面滚动触发事件的处理函数 |
onTabItemTap | Function | 当前是 tab 页时,点击 tab 时触发 |
其他 | Any | 开发者可以添加任意的函数或数据到 Object 参数中,在本页面的函数中用 this 可以访问 |
示例代码
//index.js
Page({
data: {
userName: ""
},
onLoad: function(e) {
this.getName();
},
onReady: function() {
},
onShow: function() {
},
onHide: function() {
},
onUnload: function() {
},
onPullDownRefresh: function() {
},
onReachBottom: function() {
},
onShareAppMessage: function () {
},
onPageScroll: function() {
},
onTabItemTap(item) {
console.log("当前点击的是:",JSON.stringify(item))
},
// Event handler.
getName: function() {
this.setData({
userName:"cortana"
})
},
customData: {
dName: 'cortana'
}
})
详细的参数含义和使用请参考 Page 参考文档 。
# 3.1.1 初始数据
data 是页面第一次渲染使用的初始数据。
data 中的数据必须是以下类型:字符串,数字,布尔值,对象,数组。
渲染层可以通过 FXML 对数据进行绑定。
示例代码
<view>{{userName}}</view>
<view>{{cover[0].url}}</view>
Page({
data: {
userName: 'cortana',
cover: [{url: 'http://xxxxx.jpg',title:"avatar"}, {url: 'http://xxxxx.jpg',title:"detail"}]
}
})
# 3.2 生命周期回调函数
生命周期的触发以及页面的路由方式详见
# onLoad(Object query)
页面加载时触发。一个页面仅会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数。
参数说明
名称 | 类型 | 说明 |
---|---|---|
query | Object | 打开当前页面路径中的参数 |
# onShow()
页面显示/切入前台时触发。
# onReady()
页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
注意
对界面内容进行设置的 API 如 jd.setNavigationBarTitle,请在onReady之后进行。
# onHide()
页面隐藏/切入后台时触发。 如 navigateTo 或底部 tab 切换到其他页面,小程序切入后台等。
# onUnload()
页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时。
# 3.3 页面事件处理函数
# onPullDownRefresh()
监听用户下拉刷新事件。
# onReachBottom()
监听用户上拉触底事件。
# onPageScroll(Object)
监听用户滑动页面事件。
参数说明
属性 | 类型 | 说明 |
---|---|---|
scrollTop | Number | 页面在垂直方向已滚动的距离(单位px) |
# onShareAppMessage(Object)
监听用户点击页面内分享按钮(<button>
组件 open-type="share")或右上角菜单“推荐给朋友”按钮的行为,支持自定义转发内容。此事件需要 return 一个 Object,用于配置分享的内容,详见“分享内容配置”。
Object 参数说明
属性 | 类型 | 说明 |
---|---|---|
from | String | 转发事件来源。button:页面内转发按钮;menu:右上角“推荐给朋友”菜单 |
target | Object | 如果 from 值是 button,则 target 是触发这次转发事件的 button,否则为 undefined |
webViewUrl | String | 页面中包含 |
<web-view> 组件时,返回当前 <web-view> 的url |
分享内容配置
属性 | 类型 | 必填 | 说明 |
---|---|---|---|
mpId | string | 否 | 微信小程序id,此场景用于分享到微信后,用户点击分享卡片,进入该appid对应的微信小程序,实现引流到微信小程序 |
title | string | 否 | 转发标题 |
type | number | 否 | 转发形式(0 - 微信小程序正式版 ;1 - 微信小程序开发版;2 - 微信小程序体验版; |
path | string | 否 | 小程序路径 |
mpPath | string | 否 | 微信小程序路径 |
imageUrl | string | 是 | 图片地址(小程序封面图或H5页封面) |
channel | string | 否 | 渠道(不写默认微信朋友,微信朋友圈) |
url | string | 否 | H5链接地址(H5分享填写,不填默认中间页) |
desc | string | 否 | 分享内容摘要 |
示例代码
Page({
onShareAppMessage: function (res) {
console.log(res.target);
return {
mpId: '分享的微信小程序appid',
title: '分享的标题',
type: 0,
desc: '分享的描述、摘要等',
imageUrl: 'http://pic30.finogeeks.com/20130619/9885883_210838271000_2.jpg',
path: 'page/component/index',
mpPath:'分享的微信小程序路径',
channel:'Wxfriends,Wxmoments',
url: 'https://www.finclip.com/develop/index/ao00f99475552b3131',
}
}
})
示例代码分享到微信好友后,会打开对应的正式版微信小程序,分享到朋友圈会打开url对应的H5页面。
# onTabItemTap(Object)
点击顶部、底部 tab 时触发
Object 参数说明
参数 | 类型 | 说明 |
---|---|---|
index | String | 被点击 tabItem 的序号,从 0 开始 |
pagePath | String | 被点击 tabItem 的页面路径 |
text | String | 被点击 tabItem 的按钮文字 |
示例代码
Page({
onTabItemTap(item) {
console.log("tabbar点击:",item);
}
})
# 3.4 组件事件处理函数
Page 中还可以定义组件事件处理函数。在.fxml文件中,组件中加入事件绑定,当事件被触发时,就会执行 Page 中定义的事件处理函数。
示例代码
<view bindtap="getUsername">获取用户名</view>
Page({
getUsername: function() {
console.log("点击了获取用户名")
}
})
# 3.5 使用 Component 构造器构造页面
基础库 1.6.3 开始支持
Page 构造器适用于简单的页面。但对于复杂的页面, Page 构造器可能并不好用。
此时,可以使用 Component 构造器来构造页面。 Component 构造器的主要区别是:方法需要放在 methods: { } 里面。
代码示例
Component({
data: {
text: "This is page data."
},
methods: {
onLoad: function(options) {
// 页面创建时执行
},
onPullDownRefresh: function() {
// 下拉刷新时执行
},
// 事件响应函数
viewTap: function() {
// ...
}
}
})
这种创建方式非常类似于 自定义组件 ,可以像自定义组件一样使用 behaviors 等高级特性。具体细节请阅读 Component 构造器 章节。
# Page.route
到当前页面的路径,类型为 String。
示例代码
<view bindtap="getCurrentRoute"> 点击查看当前页面路由 </view>
Page({
getCurrentRoute: function() {
console.log("当前页面route为:",this.route)
}
})
# Page.prototype.setData(Object data, Function callback)
setData 函数用于将数据用异步的方式从逻辑层发送到视图层,同时改变对应的 this.data 的值(同步)。
Object 参数说明
字段 | 类型 | 必填 | 描述 |
---|---|---|---|
data | Object | 是 | 本次要改变的数据 |
callback | Function | 否 | setData 引起的界面更新渲染完毕后的回调函数 |
Object 以 key: value 的形式表示,将 this.data 中的 key 对应的值改变成 value。
注意
- 直接修改 this.data 无法改变页面的状态的。
- 仅支持设置可 JSON 化的数据。
- 单次设置的数据量不宜过大,不能超过 1024k。
- 请不要手动把 data 中任何一项的 value 设为 undefined 。
示例代码
<!--index.fxml-->
<view>{{title}}</view>
<button bindtap="changeTitle"> 字符串类型的改变 </button>
<view>{{num}}</view>
<button bindtap="changeNum"> 数值类型的改变</button>
<view>{{array[0].name}}</view>
<button bindtap="changeArray">数组类型的改变 </button>
<view>{{object.name}}</view>
<button bindtap="changeObject">对象类型的改变 </button>
//index.js
Page({
data: {
title: '我是title',
num: 0,
array: [{name: 'cortana'}],
object: {
text: 'init data'
}
},
changeText: function() {
this.setData({
title: '新标题'
})
},
changeNum: function() {
this.setData({
num: this.data.num +1
})
},
changeArray: function() {
this.setData({
'array[0].name':'Mary'
})
},
changeObject: function(){
this.setData({
'object.name': 'Mary'
});
}
})
# 4. 页面配置
每一个小程序页面也可以使用.json文件来对本页面的窗口表现进行配置。
页面的配置只能设置 app.json 中部分 window 配置项的内容,页面中配置项会覆盖 app.json 的 window 中相同的配置项,可配置的选项如下:
属性 | 类型 | 默认值 | 描述 |
---|---|---|---|
navigationBarBackgroundColor | HexColor | #000000 | 导航栏背景颜色,如 #000000 |
navigationBarTextStyle | String | white | 导航栏标题颜色,仅支持 black、white |
navigationBarTitleText | String | 导航栏标题文字内容 | |
navigationBarTitleFixed | Boolean | false | 标题是否固定,设置为 true 则加载H5时,标题不随H5标题变更;设置为 false则会随着H5的title变更 |
backgroundColor | HexColor | #ffffff | 窗口的背景色 |
backgroundTextStyle | String | dark | 下拉 loading 的样式,仅支持 dark、light |
示例 my.json 如下:
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "个人中心",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
# 4.1 模块化
# 4.1.1 文件作用域
在.js 文件中声明的变量和方法只在当前文件中有效;不同的文件中可以声明相同名字的变量和方法。
通过全局函数 getApp() 可以获取全局的应用实例,如果需要全局的数据可以在 App() 中设置,如:
// app.js
App({
globalData: "cortana"
})
# 4.1.2 模块化
可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过 module.exports 或者 exports 才能对外暴露接口。
小程序目前暂不支持「直接引入 node_modules」,需要通过 构建 npm 完成引入工作,需要时也可直接复制代码到小程序的目录中,再进行使用。
// util.js
function getDate() {
return new Date().toLocaleTimeString()
}
module.exports.getDate = getDate
exports.getDate = getDate
在需要使用这些模块的文件中,使用 require(path) 将公共代码引入
var util = require('util.js');
Page({
getDate: function() {
let d = util.getDate();
console.log(d);
}
})
提示
值得注意的是,require 引入模块时,需要使用相对路径。
# 5 API
小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。详细介绍请参考 API 文档。
通常,在小程序 API 有以下几种类型:
# 5.1 事件监听 API
我们约定,以 on 开头的 API 用来监听某个事件是否触发,如:ft.onSocketOpen,ft.onCompassChange 等。
这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。
ft.onCompassChange(function (res) {
console.log(res.direction)
})
# 5.2 同步 API
我们约定,以 Sync 结尾的 API 都是同步 API, 如 ft.setStorageSync,ft.getSystemInfoSync 等。此外,也有一些其他的同步 API,如 ft.createWorker,ft.getBackgroundAudioManager 等,详情参见 API 文档中的说明。
同步 API 的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常。
try {
ft.setStorageSync('key', 'value')
} catch (e) {
console.error(e)
}
# 5.3 异步 API
大多数 API 都是异步 API,如 ft.request,ft.getNetworkType 等。这类 API 接口通常都接受一个 Object 类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果:
Object 参数说明
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
success | Function | 否 | 接口调用成功的回调函数) |
fail | Function | 否 | 接口调用失败的回调函数 |
complete | Function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
其他 | Any | - | 接口定义的其他参数 |
异步 API 的执行结果需要通过 Object 类型的参数中传入的对应回调函数获取。部分异步 API 也会有返回值,可以用来实现更丰富的功能,如 ft.request,ft.connectSocket 等。
ft.setStorage({
key: "key",
data: "value",
success() {
console.log('setStorage succeed');
}
})
# 5.4 异步 API 返回 Promise
基础库 3.0.25 版本起,异步 API 支持 callback & promise 两种调用方式。当接口参数 Object 对象中不包含 success/fail/complete 时将默认返回 promise,否则仍按回调方式执行,无返回值。
提示
- 部分接口如 downloadFile, request, uploadFile, connectSocket 本身就有返回值, 它们的 promisify 需要开发者自行封装。
- 当没有回调参数时,异步接口返回 promise。此时若函数调用失败进入 fail 逻辑, 会报错提示 Uncaught (in promise),开发者可通过 catch 来进行捕获。
- ft.onUnhandledRejection 可以监听未处理的 Promise 拒绝事件。
- 在安卓部分机型上由于系统原因,当使用 promisify 的方式调用,后续没有继续调用 then、catch、final 任意一个函数,当该 API 报错的时候是不会触发 onUnhandledRejection 的监听
// callback 形式调用
ft.chooseImage({
success(res) {
console.log('res:', res)
}
})
// promise 形式调用
ft.chooseImage().then(res => console.log('res: ', res))