文章目录
哈喽小伙伴们,上一期为大家讲解了一下小程序的自定义组件该如何去使用,也带着大家封装了一个带动画效果底部Tabbar组件,相信小伙伴们私底下也进行了尝试吧。今天给大家结合官方文档讲解一下小程序的架构,这篇文章主要以【逻辑层】为主,也希望大家静待我们的下篇【视图层】;让我们一起来看看吧🤘
小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。
整个小程序框架系统分为两部分:逻辑层(App Service)和 视图层(View)。小程序提供了自己的视图层描述语言 WXML 和 WXSS,以及基于 JavaScript 的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。

View层用来渲染页面结构,AppService层用来逻辑处理、数据请求、接口调用。
它们在两个线程里运行。
视图层和逻辑层通过系统层的
JSBridage
进行通信,逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理

WebView 渲染,iOS中使用自带 WKWebView,在 Android 使用腾讯的 x5内核(基于 Blink)运行。iOS 中使用自带的 JSCore 运行,在 Android中使用腾讯的 x5 内核(基于 Blink)运行。开发工具使用 nw.js 同时提供了视图层和逻辑层的运行环境。小程序开发框架的逻辑层使用 JavaScript 引擎为小程序提供开发 JavaScript 代码的运行环境以及微信小程序的特有功能。
逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
开发者写的所有代码最终将会打包成一份 JavaScript 文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似 ServiceWorker,所以逻辑层也称之为 App Service。
在 JavaScript 的基础上,我们增加了一些功能,以方便小程序的开发:
App 和 Page 方法,进行程序注册和页面注册。getApp 和 getCurrentPages 方法,分别用来获取 App 实例和当前页面栈。注意:小程序框架的逻辑层并非运行在浏览器中,因此 JavaScript 在
web中一些能力都无法使用,如window,document等
每个小程序都需要在
app.js中调用App方法注册小程序实例,绑定生命周期回调函数、错误监听和页面不存在监听函数等。
详细的参数含义和使用请参考 App 参考文档 。
// app.js
App({
onLaunch (options) {
// Do something initial when launch.
},
onShow (options) {
// Do something when show.
},
onHide () {
// Do something when hide.
},
onError (msg) {
console.log(msg)
},
globalData: 'I am global data'
})
整个小程序只有一个 App 实例,是全部页面共享的。开发者可以通过getApp方法获取到全局唯一的App 实例,获取App上的数据或调用开发者注册在 App 上的函数。
// xxx.js
const appInstance = getApp()
console.log(appInstance.globalData) // I am global data
对于小程序中的每个页面,都需要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等。
简单的页面可以使用
Page()进行构造。
代码示例:
//index.js
Page({
data: {
text: "This is page data."
},
onLoad: function(options) {
// 页面创建时执行
},
onShow: function() {
// 页面出现在前台时执行
},
onReady: function() {
// 页面首次渲染完毕时执行
},
onHide: function() {
// 页面从前台变为后台时执行
},
onUnload: function() {
// 页面销毁时执行
},
onPullDownRefresh: function() {
// 触发下拉刷新时执行
},
onReachBottom: function() {
// 页面触底时执行
},
onShareAppMessage: function () {
// 页面被用户分享时执行
},
onPageScroll: function() {
// 页面滚动时执行
},
onResize: function() {
// 页面尺寸变化时执行
},
onTabItemTap(item) {
// tab 点击时执行
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// 事件响应函数
viewTap: function() {
this.setData({
text: 'Set some data for updating view.'
}, function() {
// this is setData callback
})
},
// 自由数据
customData: {
hi: 'MINA'
}
})
详细的参数含义和使用请参考 Page 参考文档 。
页面可以引用
behaviors。 behaviors 可以用来让多个页面有相同的数据字段和方法。
// my-behavior.js
module.exports = Behavior({
data: {
sharedText: 'This is a piece of data shared between pages.'
},
methods: {
sharedMethod: function() {
this.data.sharedText === 'This is a piece of data shared between pages.'
}
}
})
// page-a.js
var myBehavior = require('./my-behavior.js')
Page({
behaviors: [myBehavior],
onLoad: function() {
this.data.sharedText === 'This is a piece of data shared between pages.'
}
})
具体用法参见 behaviors 。
Page 构造器适用于简单的页面。但对于复杂的页面, Page 构造器可能并不好用。
此时,可以使用
Component构造器来构造页面。Component构造器的主要区别是:方法需要放在methods: { }里面。
代码示例:
Component({
data: {
text: "This is page data."
},
methods: {
onLoad: function(options) {
// 页面创建时执行
},
onPullDownRefresh: function() {
// 下拉刷新时执行
},
// 事件响应函数
viewTap: function() {
// ...
}
}
})
这种创建方式非常类似于 自定义组件 ,可以像自定义组件一样使用 behaviors 等高级特性。
具体细节请阅读我们的上一篇博文 Component 构造器 章节。
小程序里生命周期分为两部分:
小程序生命周期和页面生命周期,后续会专门出一篇文章去详细讲解的。
下图说明了页面 Page 实例的生命周期。

在小程序中所有页面的路由全部由框架进行管理。
框架以栈的形式维护了当前的所有页面。 当发生路由切换的时候,页面栈的表现如下:
| 路由方式 | 页面栈表现 |
|---|---|
| 初始化 | 新页面入栈 |
| 打开新页面 | 新页面入栈 |
| 页面重定向 | 当前页面出栈,新页面入栈 |
| 页面返回 | 页面不断出栈,直到目标返回页 |
| Tab 切换 | 页面全部出栈,只留下新的 Tab 页面 |
| 重加载 | 页面全部出栈,只留下新的页面 |
开发者可以使用 getCurrentPages() 函数获取当前页面栈。
对于路由的触发方式以及页面生命周期函数如下:
| 路由方式 | 触发时机 | 路由前页面 | 路由后页面 |
|---|---|---|---|
| 初始化 | 小程序打开的第一个页面 | onLoad, onShow | |
| 打开新页面 | 调用 API wx.navigateTo,使用组件 <navigator open-type="navigateTo"/> | onHide | onLoad, onShow |
| 页面重定向 | 调用 API wx.redirectTo,使用组件 <navigator open-type="redirectTo"/> | onUnload | onLoad, onShow |
| 页面返回 | 调用 API wx.navigateBack,使用组件<navigator open-type="navigateBack">,用户按左上角返回按钮 | onUnload | onShow |
| Tab 切换 | 调用 API wx.switchTab使用组件 ,<navigator open-type="switchTab"/>,用户切换 Tab | 各种情况请参考下表 | |
| 重启动 | 调用 API wx.reLaunch,使用组件 <navigator open-type="reLaunch"/> | onUnload | onLoad, onShow |
Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例):
| 当前页面 | 路由后页面 | 触发的生命周期(按顺序) |
|---|---|---|
| A | A | Nothing happend |
| A | B | A.onHide(), B.onLoad(), B.onShow() |
| A | B(再次打开) | A.onHide(), B.onShow() |
| C | A | C.onUnload(), A.onShow() |
| C | B | C.onUnload(), B.onLoad(), B.onShow() |
| C | B | D.onUnload(), C.onUnload(), B.onLoad(), B.onShow() |
| D(从转发进入) | A | D.onUnload(), A.onLoad(), A.onShow() |
| D(从转发进入) | B | D.onUnload(), B.onLoad(), B.onShow() |
注意事项
navigateTo, redirectTo 只能打开非 tabBar 页面。switchTab 只能打开 tabBar 页面。reLaunch 可以打开任意页面。tabBar由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。onLoad中获取。可以将一些公共的代码抽离成为一个单独的 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 将公共代码引入
var common = require('common.js')
Page({
helloMINA: function() {
common.sayHello('MINA')
},
goodbyeMINA: function() {
common.sayGoodbye('MINA')
}
})
在 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 localValue in a.js.
var localValue = 'b'
// If a.js it run before b.js, now the globalData shoule be 2.
console.log(getApp().globalData)
小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。详细介绍请参考 API 文档。
通常,在小程序 API 有以下几种类型:
我们约定,以 on 开头的 API 用来监听某个事件是否触发,如:wx.onSocketOpen,wx.onCompassChange 等。
这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。
代码示例:
wx.onCompassChange(function (res) {
console.log(res.direction)
})
我们约定,以
Sync结尾的 API 都是同步 API, 如wx.setStorageSync,wx.getSystemInfoSync等。此外,也有一些其他的同步 API,如wx.createWorker,wx.getBackgroundAudioManager等,详情参见 API 文档中的说明。同步 API 的执行结果可以通过函数返回值直接获取,如果执行出错会抛出异常。
代码示例:
try {
wx.setStorageSync('key', 'value')
} catch (e) {
console.error(e)
}
大多数 API 都是异步 API,如 wx.request,wx.login 等。这类 API 接口通常都接受一个 Object 类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| success | function | 否 | 接口调用成功的回调函数 |
| fail | function | 否 | 接口调用失败的回调函数 |
| complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
| 其他 | Any | - | 接口定义的其他参数 |
success,fail,complete函数调用时会传入一个Object 类型参数,包含以下字段:
| 属性 | 类型 | 说明 |
|---|---|---|
| errMsg | string | 错误信息,如果调用成功返回 ${apiName}:ok |
| errCode | number | 错误码,仅部分 API 支持,具体含义请参考对应 API 文档,成功时为 0。 |
| 其他 | Any | 接口返回的其他数据 |
异步 API的执行结果需要通过Object 类型的参数中传入的对应回调函数获取。部分异步 API 也会有返回值,可以用来实现更丰富的功能,如wx.request,wx.connectSocket等。
代码示例
wx.login({
success(res) {
console.log(res.code)
}
})
基础库 2.10.2 版本起,异步 API 支持
callback&promise两种调用方式。当接口参数 Object 对象中不包含success/fail/complete时将默认返回promise,否则仍按回调方式执行,无返回值。
注意事项
1.部分接口如 downloadFile, request, uploadFile, connectSocket, createCamera(小游戏)本身就有返回值, 它们的 promisify 需要开发者自行封装。
2.当没有回调参数时,异步接口返回 promise。此时若函数调用失败进入 fail 逻辑, 会报错提示 Uncaught (in promise),开发者可通过 catch 来进行捕获。
3.wx.onUnhandledRejection 可以监听未处理的 Promise 拒绝事件。
代码示例
// callback 形式调用
wx.chooseImage({
success(res) {
console.log('res:', res)
}
})
// promise 形式调用
wx.chooseImage().then(res => console.log('res: ', res))
开通并使用微信云开发,即可使用
云开发API,在小程序端直接调用服务端的云函数。
代码示例
wx.cloud.callFunction({
// 云函数名称
name: 'cloudFunc',
// 传给云函数的参数
data: {
a: 1,
b: 2,
},
success: function(res) {
console.log(res.result) // 示例
},
fail: console.error
})
// 此外,云函数同样支持promise形式调用
这篇为小伙伴们讲解小程序的架构之【逻辑层】,下期为大家讲解【视图层】的知识;水滴石穿,积少成多。各位小伙伴让我们 let’s be prepared at all times!
✨原创不易,还希望各位大佬支持一下!
👍 点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否
是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在