[原创] 分享我们自己搭建的微信小程序开发框架——wframe及设计思想详解
wframe不是控件库,也不是UI库,她是一个微信小程序面向对象编程框架,代码只有几百行。她的主要功能是规范小程序项目的文件结构、规范应用程序初始化、规范页面加载及授权管理的框架,当然,wframe也提供了一些封装好了的函数库,方便开发者调用。
wframe目前已实现的核心功能:
1. 应用程序初始化自动从服务器获取配置,ajax成功后触发ready事件;
2. 每个页面对象可以配置是否requireLogin属性,如果需要登录,则每个页面在进入ready方法之前会自动完成授权、获取用户信息、服务器端登录;
3. 完成ajax全局封装:如果用户已经登录,则会自动在http-header添加token信息,如果session过期则会重新进入登录流程;
本文的阅读对象:想要自己搭建小程序框架的人,相信本文会给你提供一些思路。
我们为什么要开发wframe?
我们开发的小程序越来越多,小程序也越来越复杂,于是我们就想将每个小程序重复在写的那一部分代码提出来,变成一个公共的函数库,一个跟每个项目的业务逻辑完全不相关的函数库。除了在新项目中可以节省代码之外,有一些复杂的代码逻辑由于提到了公共的函数库,我们将其优化得更优雅、更健壮。
说wframe是一个函数库虽说也可以,但wframe更像一个框架。我们通常把一些静态方法、静态对象、仅处理页面内容的JS文件集称作函数库,比如jQuery;我们通常把处理了应用程序和页面生命周期,以及使用了大量的面向对象编程技术的JS文件集称作框架。因此,wframe其实是一个框架。
重要说明:wframe框架用到了大量的面向对象编程知识,比如实例、继承、覆写、扩展、抽象方法等等,因此对开发人员,特别是项目中的架构师,的面向对象编程能力有较高要求。
项目源码已上传到GitHub并会持续更新:https://github.com/leotsai/wframe
一、wframe项目结构
wframe的最核心的职责就是规范项目文件结构。
为什么需要规范呢?因为我们小程序越来越多,如果每个小程序的文件结构都不一样的话,那定是一件很难受的事。另外,wframe由于其框架的身份,其本职工作就是定义一个最好的文件结构,这样基于wframe创建的所有小程序都将自动继承wframe的优秀品质。
1. _core文件夹
wframe框架源码,与业务毫不相干,每个小程序都可以直接将_core文件夹复制到项目中,而当wframe更新版本时,所有小程序可以直接覆盖_core完成升级。用下划线“_”开头的目的有2个:
(a) 将此文件夹置顶;
(b) 标记此文件夹是一个特殊文件夹,本框架中还有其他地方也会用到下划线开头为文件夹/文件。
2. _demo文件夹
业务核心文件夹,比如定义一些扩展wframe框架的类,同时这些类又被具体的业务类继承使用,比如ViewModelBase等。
3. pages文件夹
与微信小程序官方文档定义一致:放置页面的地方。
4. app.js
程序主入口,只不过基于wframe的小程序的app.js跟官方的长得很不一样,我们定义了一个自己的Applicaiton类,然后再new的一个Application实例。稍后详解。
5. mvcApp.js
几乎每个js文件都会require引入的一个文件,因为这相当于是项目的静态入口,其包含了所有的静态函数库,比如对wx下面方法的封装、Array类扩展、Date类扩展、网络请求(ajax)封装等等。mvcApp.js几乎只定义了一个入口,其内部的很多对象、方法都是通过require其他JS引入的。因此,大多数情况下,我们只需要require引入mvcApp.js就够了。
写到这里,分享一个我们的编程思想:入口要少。小程序里有哪些入口:this、getApp()、wx、mvcApp。其实也就是每一行代码点号“.”前面的都叫代码入口。
我们还有另一个编程规范(强制):每个文件不能超过200行代码(最好不超100行)。这就是要求每个程序员必须学会拆分,拆分也是我们的另一个编程思想。通过拆分,每个JS文件职责清晰,极大的提高了代码阅读率。
二、详解
1. app.js和Application类详解
app.js定义了程序入口。
1 var mvcApp = require('mvcApp.js'); 2 var Application = require('_core/Application.js'); 3 4 function MvcApplication() { 5 Application.call(this); 6 this.initUrl = 'https://www.somdomain.com/api/client-config/get?key=wx_applet_wframe'; 7 this.host = 'http://localhost:18007'; 8 this.confgis = { 9 host: 'http://localhost:18007', 10 cdn: 'https://images.local-dev.cdn.somedomain.com' 11 }; 12 this.mock = true; 13 this.accessToken = null; 14 this.useDefaultConfigsOnInitFailed = false; 15 }; 16 17 MvcApplication.prototype = new Application(); 18 19 MvcApplication.prototype.onInitialized = function (configs) { 20 if (configs != null && configs !== '') { 21 this.configs = JSON.parse(configs); 22 this.host = this.configs.host; 23 } 24 }; 25 26 App(new MvcApplication());
可以看到app.js定义了一个MvcApplication类,继承自框架中的Application类,同时重写了父类的onInitialized方法。
下面是框架中的Application类:
1 var WebClient = require('http/WebClient.js'); 2 var AuthorizeManager = require('weixin/AuthorizeManager.js'); 3 var weixin = require('weixin.js'); 4 5 6 function Application() { 7 this.initUrl = ''; 8 this.host = ''; 9 this.session = null; 10 this.initialized = false; 11 this.mock = false; 12 this.useDefaultConfigsOnInitFailed = false; 13 this.authorizeManager = new AuthorizeManager(); 14 this._userInfo = null; 15 this._readyHandlers = []; 16 }; 17 18 Application.prototype = { 19 onLaunch: function () { 20 var me = this; 21 if(this.initUrl === ''){ 22 throw 'please create YourOwnApplication class in app.js that inerits from Application class and provide initUrl in constructor'; 23 } 24 var client = new WebClient(); 25 client.post(this.initUrl, null, function(result){ 26 if (result.success || me.useDefaultConfigsOnInitFailed){ 27 me.initialized = true; 28 me.onInitialized(result.success ? result.value : null); 29 me.triggerReady(); 30 } 31 else{ 32 weixin.alert('小程序初始化失败', result.message); 33 } 34 }, '初始化中...'); 35 }, 36 onShow: function () { 37 38 }, 39 onHide: function () { 40 41 }, 42 onError: function () { 43 44 }, 45 onPageNotFound: function () { 46 47 }, 48 ready: function (callback) { 49 var me = this; 50 if (this.initialized === true) { 51 callback && callback(); 52 return; 53 } 54 this._readyHandlers.push(callback); 55 }, 56 triggerReady: function () { 57 for (var i = 0; i < this._readyHandlers.length; i++) { 58 var callback = this._readyHandlers[i]; 59 callback && callback(); 60 } 61 this._readyHandlers = []; 62 }, 63 onInitialized: function(configs){ 64 65 }, 66 getUserInfo: function(callback){ 67 var me = this; 68 if(this._userInfo != null){ 69 callback && callback(this._userInfo.userInfo); 70 return; 71 } 72 this.authorizeManager.getUserInfo(function(result){ 73 me._userInfo = result; 74 callback && callback(me._userInfo.userInfo); 75 }); 76 }, 77 getCurrentPage: function(){ 78 var pages = getCurrentPages(); 79 return pages.length > 0 ? pages[0] : null; 80 } 81 }; 82 83 module.exports = Application;
Applicaiton类(及其子类)在wframe框架中的主要工作:
1. 应用程序初始化的时候从服务器获取一个配置,比如服务器域名(实现域名实时切换)、CDN域名,以及其他程序配置信息;
2. 全局存储用户的授权信息和登陆之后的会话信息;
3. 全局mock开关;
4. 其他快捷方法,比如获取当前页面等。
Application类核心执行流程:
1. 应用程序初始化时首先从服务器获取客户端配置信息;
2. 获取完成之后会触发onInitialized方法(在子类中覆写)和ready方法。
2. PageBase类详解
PageBase类是所有页面都会继承的一个基类。先看代码:
1 console.log("PageBae.js entered"); 2 3 const app = getApp(); 4 5 function PageBase(title) { 6 this.vm = null; 7 this.title = title; 8 this.requireLogin = true; 9 }; 10 11 PageBase.prototype = { 12 onLoad: function (options) { 13 var me = this; 14 if (this.title != null) { 15 this.setTitle(this.title); 16 } 17 this.onPreload(options); 18 app.ready(function () { 19 if (me.requireLogin && app.session == null) { 20 app.getUserInfo(function (info) { 21 me.login(info, function (session) { 22 app.session = session; 23 me.ready(options); 24 }); 25 }); 26 } 27 else { 28 me.ready(options); 29 } 30 }); 31 }, 32 ready: function (options) { 33 34 }, 35 onPreload: function(options){ 36 37 }, 38 render: function () { 39 var data = {}; 40 for (var p in this.vm) { 41 var value = this.vm[p]; 42 if (!this.vm.hasOwnProperty(p)) { 43 continue; 44 } 45 if (value == null || typeof (value) === 'function') { 46 continue; 47 } 48 if (value.__route__ != null) { 49 continue; 50 } 51 data[p] = this.vm[p]; 52 } 53 this.setData(data); 54 }, 55 go: function (url, addToHistory) { 56 if (addToHistory === false) { 57 wx.redirectTo({ url: url }); 58 } 59 else { 60 wx.navigateTo({ url: url }); 61 } 62 }, 63 goBack: function () { 64 wx.navigateBack({}); 65 }, 66 setTitle: function (title) { 67 this.title = title; 68 wx.setNavigationBarTitle({ title: this.title }); 69 }, 70 login: function (userInfo, callback) { 71 throw 'please implement PageBase.login method.'; 72 }, 73 getFullUrl: function () { 74 var url = this.route.indexOf('/') === 0 ? this.route : '/' + this.route; 75 var parts = []; 76 for (var p in this.options) { 77 if (this.options.hasOwnProperty(p)) { 78 parts.push(p + "=" + this.options[p]); 79 } 80 } 81 if (parts.length > 0) { 82 url += "?" + parts.join('&'); 83 } 84 return url; 85 }, 86 isCurrentPage: function(){ 87 return this === getApp().getCurrentPage(); 88 } 89 }; 90 91 PageBase.extend = function (prototypeObject) { 92 var fn = new PageBase(); 93 for (var p in prototypeObject) { 94 fn[p] = prototypeObject[p]; 95 } 96 return fn; 97 }; 98 99 module.exports = PageBase;
由于微信小程序Application类的onLaunch不支持回调,也就是说,在wframe框架中,虽然我们在onLaunch时发起了ajax调用,但是程序并不会等待ajax返回就会立即进入Page对象的onLoad方法。这是一个非常重要的开发小程序的知识前提,但是官方文档并没有重要说明。
PageBase类的三个实例属性:
1. vm:即ViewModel实例,可以理解为官方文档中的Page实例的data属性;
2. title:页面标题
3. requireLogin:是否需要登录,如果设置为true,则页面onLoad执行后自动进入登录流程,登录完成后才会触发页面的ready方法;
PageBase类的实例方法:
1. onLoad:对应官方文档中的onLoad事件。wframe框架自动会处理requireLogin属性,处理完成后才触发ready方法;
2. ready:每个业务级页面的主入口,每个业务级页面都应该实现ready方法,而不一定实现onLoad方法;
3. onPreload:在执行onLoad之前执行的方法,不支持异步;
4. render:非常常用的方法,功能是将ViewModel(即data)呈现到页面上,在业务页面中直接使用this.render()即可将更新的数据呈现出来;
5. go:页面跳转,相比官方的wx.navigateTo简化了很多;
6. goBack:等于wx.navigateBack;
7. setTitle:直接设置页面标题;
8. login:可以理解成抽象方法,必须由子类实现,在我们demo中由业务级框架中的DemoPageBase实现;
9. getFullUrl:获取页面完整地址,包括路径和参数,便于直接跳转;
10. isCurrentPage:判断该页面实例是否在应用程序页面栈中处于当前页面,主要用于setInterval函数中判断用户是否已离开了页面;
3. DemoPageBase类详解
这是业务层级的框架内容。我们建议每个页面都继承自该类,这个类可以封装跟业务相关的很多逻辑,方便子类(业务页面)直接通过this调用相关方法。
在wframe的demo框架中,我们实现了PageBase类的抽象方法login。
这里请注意同目录的api.js文件。在我们的编码规范中,所有ajax访问都需要提到专门的api.js文件,通常与页面类处于同一目录,这是为了方便mock API。请看示例代码:
1 var mvcApp = require('../mvcApp.js'); 2 3 var api = { 4 login: function (userInfo, code, callback) { 5 var data = mvcApp.serializeToKeyValues(userInfo) + "&code=" + code; 6 mvcApp.ajax.busyPost('/demo/api/login', data, function(result){ 7 callback(result.value); 8 }, '登陆中...', true); 9 } 10 }; 11 if (getApp().mock) { 12 var api = { 13 login: function (userInfo, code, callback) { 14 setTimeout(function(){ 15 callback({ 16 token: '98c2f1bd7beb3bef3b796a5ebf32940498cb5586ddb4a5aa8e' 17 }); 18 }, 2000); 19 } 20 }; 21 } 22 23 module.exports = api;
4. 页面类的实现
请看pages/index目录下的文件列表:
1. IndexViewModel:该页面的ViewModel;
2. api.js:该页面所有ajax的封装;
3. index.js:页面入口;
4. index.wxml:HTML;
5. index.wxss:样式;
先看入口index.js,代码如下:
1 var mvcApp = require('../../mvcApp.js'); 2 var DemoPageBase = require('../DemoPageBase.js'); 3 var IndexViewModel = require('IndexViewModel.js'); 4 5 function IndexPage() { 6 DemoPageBase.call(this, 'index'); 7 }; 8 9 IndexPage.prototype = new DemoPageBase(); 10 11 IndexPage.prototype.onPreload = function(options){ 12 this.vm = new IndexViewModel(this); 13 this.render(); 14 }; 15 16 IndexPage.prototype.ready = function () { 17 var me = this; 18 this.vm.load(); 19 }; 20 21 IndexPage.prototype.goDetails = function (e) { 22 var item = e.target.dataset.item; 23 wx.navigateTo({ 24 url: '/pages/details/details?id=' + item.id 25 }); 26 }; 27 28 Page(new IndexPage());
index.js核心逻辑:继承自DemoPageBase,onPreload时设置了ViewModel,ready时(自动登录完成后)调用ViewModel的数据加载方法,完成。
5. ViewModel的实现
在微信小程序官方文档中,并没有提ViewModel的概念,这会导致一些稍微有点复杂的页面的data对象的处理变得很凌乱,更别说复杂页面的data处理,那根本无从维护。ViewModel的设计思想是专门用来封装视图数据的一层代码,不管是MVC,还是MVVM,ViewModel都是拆分数据层代码的最佳实践。因此,wframe框架强烈建议每个页面都建一个对应的ViewModel,封装数据结构,以及获取、处理数据。
在我们的编程思想中,ViewModel不仅仅是放数据的地方,更是封装业务逻辑的最佳位置之一。所以我们的ViewModel会很肥(fat model),会包含相关的很多业务逻辑处理。
如果项目需要,还可以封装一个DemoViewModelBase类,将其他页面ViewModel常用的方法封装进来,比如this.getUserName()等方法。
请看示例代码:
1 var api = require('api.js'); 2 var mvcApp = require('../../mvcApp.js'); 3 4 function IndexViewModel(page){ 5 this.users = []; 6 this.showLoading = true; 7 this.males = 0; 8 this.females = 0; 9 this.page = page; 10 }; 11 12 IndexViewModel.prototype.load = function(){ 13 var me = this; 14 api.getUsers(function(users){ 15 me.showLoading = false; 16 me.females = users._count(function(x){ 17 return x.gender === 'female'; 18 }); 19 me.males = users._count(function (x) { 20 return x.gender === 'male'; 21 }); 22 me.users = users._orderByDescending(null, function(first, second){ 23 if(first.gender === 'male'){ 24 if(second.gender === 'male'){ 25 return first.birthYear > second.birthYear; 26 } 27 return true; 28 } 29 if(second.gender === 'female'){ 30 return first.birthYear > second.birthYear; 31 } 32 return false; 33 }); 34 me.page.render(); 35 }); 36 }; 37 38 module.exports = IndexViewModel;
api.js就不贴代码了,跟上一小节中的api.js一样的。html和css部分也忽略不讲。
至此,页面级实现就完成了。
下面,笔者再对wframe框架中的其他特殊部分进行特殊说明。继续。
6. pages/_authorize文件夹
这个文件夹定义了一个授权页面,这是因为新版小程序API强制要求用户自己点授权按钮才能弹出授权。这个虽然集成在wframe框架中,但是每个项目应该自行修改此页面的样式以符合项目UI设计。
这个目录下面只有一个_authorize.js值得贴一下代码,其实都非常简单:
1 var DemoPageBase = require('../DemoPageBase.js'); 2 3 4 function AuthPage() { 5 DemoPageBase.call(this, 'auth'); 6 this.requireLogin = false; 7 }; 8 9 AuthPage.prototype = new DemoPageBase(); 10 11 AuthPage.prototype.onPreload = function (options) { 12 this.returnUrl = decodeURIComponent(options.returnUrl); 13 }; 14 15 AuthPage.prototype.onGotUserInfo = function (event) { 16 var me = this; 17 if (event.detail.userInfo == null) { 18 return; 19 } 20 var app = getApp(); 21 app._userInfo = event.detail; 22 DemoPageBase.prototype.login.call(this, app._userInfo.userInfo, function () { 23 me.go(me.returnUrl, false); 24 }) 25 } 26 27 Page(new AuthPage())
请注意onPreload方法中对returnUrl的获取,以及获取用户授权信息后对DemoPageBase.login方法的调用。
7. _core文件夹其他文件详解
_core文件夹之前已经讲了Application和PageBase类。继续。
1. weixin.js
主要封装了toast、busy(增加延时功能)、alert、confirm方法,后期可能会增加更多常用方法的封装。代码如下:
1 var weixin = { 2 _busyTimer: null, 3 _busyDelay: 1500, 4 toast: function (message, icon) { 5 wx.showToast({ 6 title: message, 7 icon: icon == null || icon == '' ? 'none' : icon 8 }); 9 }, 10 toastSuccess: function (message) { 11 this.toast(message, 'success'); 12 }, 13 busy: function (option, delay) { 14 clearTimeout(this._busyTimer); 15 if (option === false) { 16 wx.hideLoading(); 17 return; 18 } 19 if (delay === 0) { 20 wx.showLoading({ 21 title: option, 22 mask: true 23 }); 24 } 25 else { 26 this._busyTimer = setTimeout(function () { 27 wx.showLoading({ 28 title: option, 29 mask: true 30 }); 31 }, delay == null ? this._busyDelay : delay); 32 } 33 }, 34 alert: function (title, content, callback) { 35 content = content == undefined ? '' : content; 36 wx.showModal({ 37 title: title, 38 content: content, 39 showCancel: false, 40 confirmText: "确定", 41 success: res => { 42 callback && callback(); 43 } 44 }); 45 }, 46 confirm: function (title, content, buttons) { 47 var buttonList = []; 48 for (var p in buttons) { 49 if (buttons.hasOwnProperty(p)) { 50 buttonList.push({ 51 text: p, 52 handler: buttons[p] 53 }) 54 } 55 } 56 content = content == undefined ? '' : content; 57 wx.showModal({ 58 title: title, 59 content: content, 60 showCancel: true, 61 cancelText: buttonList[0].text, 62 confirmText: buttonList[1].text, 63 success: res => { 64 if (res.confirm) { 65 buttonList[1].handler && buttonList[1].handler(); 66 } else if (res.cancel) { 67 buttonList[0].handler && buttonList[0].handler(); 68 } 69 } 70 }); 71 } 72 }; 73 74 module.exports = weixin;
2. extensions/ArrayExtensions.js
一大堆数组扩展方法,非常常用,非常好用。引入mvcApp的业务层代码均可直接使用。代码如下:
1 var ArrayExtensions = {}; 2 3 Array.prototype._each = function (func) { 4 for (var i = 0; i < this.length; i++) { 5 var item = this[i]; 6 var result = func(i, item); 7 if (result === false) { 8 return; 9 } 10 } 11 }; 12 13 Array.prototype._sum = function (propertyOrFunc) { 14 var total = 0; 15 var isFunc = typeof (propertyOrFunc) == "function"; 16 this._each(function (i, item) { 17 if (isFunc) { 18 total += propertyOrFunc(item); 19 } else { 20 var value = item[propertyOrFunc]; 21 if (value != undefined) { 22 value = value * 1; 23 if (!isNaN(value)) { 24 total += value; 25 } 26 } 27 } 28 }); 29 return total; 30 }; 31 32 Array.prototype._where = function (predicateFunction) { 33 var results = new Array(); 34 this._each(function (i, item) { 35 if (predicateFunction(item)) { 36 results.push(item); 37 } 38 }); 39 return results; 40 }; 41 42 Array.prototype._orderBy = function (property, isFirstGreaterThanSecondFunction) { 43 var items = this; 44 for (var i = 0; i < items.length - 1; i++) { 45 for (var j = 0; j < items.length - 1 - i; j++) { 46 if (isFirstGreaterThanSecond(items[j], items[j + 1])) { 47 var temp = items[j + 1]; 48 items[j + 1] = items[j]; 49 items[j] = temp; 50 } 51 } 52 } 53 function isFirstGreaterThanSecond(first, second) { 54 if (isFirstGreaterThanSecondFunction != undefined) { 55 return isFirstGreaterThanSecondFunction(first, second); 56 } 57 else if (property == undefined || property == null) { 58 return first > second; 59 } 60 else { 61 return first[property] > second[property]; 62 } 63 } 64 65 return items; 66 }; 67 68 Array.prototype._orderByDescending = function (property, isFirstGreaterThanSecondFunction) { 69 var items = this; 70 for (var i = 0; i < items.length - 1; i++) { 71 for (var j = 0; j < items.length - 1 - i; j++) { 72 if (!isFirstGreaterThanSecond(items[j], items[j + 1])) { 73 var temp = items[j + 1]; 74 items[j + 1] = items[j]; 75 items[j] = temp; 76 } 77 } 78 } 79 function isFirstGreaterThanSecond(first, second) { 80 if (isFirstGreaterThanSecondFunction != undefined) { 81 return isFirstGreaterThanSecondFunction(first, second); 82 } 83 else if (property == undefined || property == null) { 84 return first > second; 85 } 86 else { 87 return first[property] > second[property]; 88 } 89 } 90 91 return items; 92 }; 93 94 Array.prototype._groupBy = function (property) { 95 var results = []; 96 var items = this; 97 98 var keys = {}, index = 0; 99 for (var i = 0; i < items.length; i++) { 100 var selector; 101 if (typeof property === "string") { 102 selector = items[i][property]; 103 } else { 104 selector = property(items[i]); 105 } 106 if (keys[selector] === undefined) { 107 keys[selector] = index++; 108 results.push({ key: selector, value: [items[i]] }); 109 } else { 110 results[keys[selector]].value.push(items[i]); 111 } 112 } 113 return results; 114 }; 115 116 Array.prototype._skip = function (count) { 117 var items = new Array(); 118 for (var i = count; i < this.length; i++) { 119 items.push(this[i]); 120 } 121 return items; 122 }; 123 124 Array.prototype._take = function (count) { 125 var items = new Array(); 126 for (var i = 0; i < this.length && i < count; i++) { 127 items.push(this[i]); 128 } 129 return items; 130 }; 131 132 Array.prototype._firstOrDefault = function (predicateFunction) { 133 if (this.length == 0) { 134 return null; 135 } 136 if (predicateFunction == undefined) { 137 return this[0]; 138 } 139 var foundItem = null; 140 this._each(function (i, item) { 141 if (predicateFunction(item)) { 142 foundItem = item; 143 return false; 144 } 145 return true; 146 }); 147 return foundItem; 148 }; 149 150 Array.prototype._any = function (predicateFunction) { 151 if (predicateFunction == undefined) { 152 return this.length > 0; 153 } 154 var hasAny = false; 155 this._each(function (i, item) { 156 if (predicateFunction(item)) { 157 hasAny = true; 158 return false; 159 } 160 return true; 161 }); 162 return hasAny; 163 }; 164 165 Array.prototype._select = function (newObjectFunction) { 166 if (newObjectFunction == undefined) { 167 throw "parameter newObjectFunction cannot be null or undefined"; 168 } 169 var items = []; 170 this._each(function (i, item) { 171 items.push(newObjectFunction(item)); 172 }); 173 return items; 174 }; 175 176 Array.prototype._insert = function (index, item) { 177 this.splice(index, 0, item); 178 }; 179 180 Array.prototype._insertMany = function (index, items) { 181 if (items == null) { 182 return; 183 } 184 for (var i = 0; i < items.length; i++) { 185 this._insert(index + i, items[i]); 186 } 187 }; 188 189 Array.prototype._add = function (item) { 190 this.push(item); 191 }; 192 193 Array.prototype._addMany = function (items) { 194 if (items == null) { 195 return; 196 } 197 for (var i = 0; i < items.length; i++) { 198 this.push(items[i]); 199 } 200 }; 201 202 Array.prototype._clear = function () { 203 this.splice(0, this.length); 204 }; 205 206 Array.prototype._count = function (predicateFunction) { 207 var count = 0; 208 this._each(function (i, item) { 209 if (predicateFunction(item)) { 210 count++; 211 } 212 }); 213 return count; 214 }; 215 216 217 /************************************** */ 218 module.exports = ArrayExtensions;
3. http/WebClient.js
封装网络请求,我们叫ajax。增加busy、header、自动异常处理等逻辑。非常常用,非常好用。代码如下:
1 var weixin = require('../weixin.js'); 2 3 function WebClient() { 4 this.busyText = null; 5 this.busyDelay = 1500; 6 this.url = ''; 7 this.data = null; 8 this.method = 'GET'; 9 this.contentType = 'application/x-www-form-urlencoded'; 10 this.dataType = 'json'; 11 this.onlyCallbackOnSuccess = false; 12 this._request = null; 13 this._callback = null; 14 this._header = {}; 15 }; 16 17 WebClient.prototype = { 18 setHeader: function(key, value){ 19 this._header[key] = value; 20 }, 21 removeHeader: function(key){ 22 delete this.header[key]; 23 }, 24 get: function (url, callback, busyText, onlyCallbackOnSuccess){ 25 this.method = 'GET'; 26 this.url = url; 27 this.data = null; 28 this._callback = callback; 29 this.busyText = busyText; 30 this.onlyCallbackOnSuccess = onlyCallbackOnSuccess == null ? false : onlyCallbackOnSuccess; 31 this.execute(); 32 }, 33 post: function (url, data, callback, busyText, onlyCallbackOnSuccess) { 34 this.method = 'POST'; 35 this.url = url; 36 this.data = data; 37 this._callback = callback; 38 this.busyText = busyText; 39 this.onlyCallbackOnSuccess = onlyCallbackOnSuccess == null ? false : onlyCallbackOnSuccess; 40 this.execute(); 41 }, 42 execute: function () { 43 var me = this; 44 if (this.busyText != null && this.busyText !== '') { 45 weixin.busy(me.busyText, me.busyDelay); 46 } 47 this._request = wx.request({ 48 url: me.url, 49 data: me.data, 50 method: me.method, 51 header: me._getRequestHeader(), 52 success: (response) => { 53 if (me.busyText != null) { 54 weixin.busy(false); 55 } 56 me._onResponse(response); 57 }, 58 fail: res => { 59 if (me.busyText != null) { 60 weixin.busy(false); 61 } 62 me._handleError({ statusCode: 500 }); 63 } 64 }); 65 }, 66 _getRequestHeader: function(){ 67 var header = {}; 68 if(this.contentType != null && this.contentType !== ''){ 69 header['content-type'] = this.contentType; 70 } 71 for(var p in this._header){ 72 if(this._header.hasOwnProperty(p)){ 73 header[p] = this._header[p]; 74 } 75 } 76 return header; 77 }, 78 _onResponse: function (response) { 79 if (response.statusCode === 200) { 80 if (this.onlyCallbackOnSuccess === false) { 81 this._callback && this._callback(response.data); 82 } else { 83 if (response.data.success === true) { 84 this._callback && this._callback(response.data); 85 } else { 86 weixin.alert("提示", response.data.message); 87 } 88 } 89 } 90 else { 91 this._handleError(response); 92 } 93 }, 94 _handleError: function (response) { 95 if (response.statusCode === 0 && err.statusText === "abort") { 96 return; 97 } 98 if (this.onlyCallbackOnSuccess) { 99 weixin.alert("网络错误", "错误码:" + response.statusCode); 100 } else { 101 this._callback && this._callback({ 102 success: false, 103 message: "网络错误:" + response.statusCode, 104 code: response.statusCode 105 }); 106 } 107 } 108 }; 109 110 module.exports = WebClient;
4. weixin/AuthorizeManager.js
wframe框架自带的授权管理器,在Application初始化时已赋值到Application.authorizeManager实例属性上面,因此,如果想要自定义实现AuthorizeManager,那么可以继承框架中的默认AuthorizeManager,然后再重写部分方法,然后在初始化Applicaiton的时候注入不同的实现类即可。
这个类的实例已经添加到Application实例,所以可以通过 getApp().authorizeManager.authorize('your-scope-name', callback) 弹出授权。
1 var weixin = require('../weixin.js'); 2 3 4 5 function AuthorizeManager() { 6 this.pageUrl = '/pages/_authorize/_authorize'; 7 }; 8 9 AuthorizeManager.scopes = { 10 userInfo: 'scope.userInfo' 11 }; 12 13 AuthorizeManager.prototype = { 14 authorize: function (scope, callback) { 15 var me = this; 16 me._isAuthorized(scope, function (authorized) { 17 if (authorized) { 18 callback(); 19 } 20 else { 21 me._showAuthorize(scope, callback); 22 } 23 }); 24 }, 25 getUserInfo: function (callback) { 26 var me = this; 27 var scope = AuthorizeManager.scopes.userInfo; 28 function handleAuthorized() { 29 wx.getUserInfo({ 30 success: function (res) { 31 callback && callback(res); 32 }, 33 fail: function (res) { 34 var url = getApp().getCurrentPage().getFullUrl(); 35 wx.redirectTo({ 36 url: me.pageUrl + "?returnUrl=" + encodeURIComponent(url) 37 }); 38 } 39 }) 40 }; 41 me.authorize(scope, handleAuthorized); 42 }, 43 _isAuthorized: function (scope, callback) { 44 wx.getSetting({ 45 success: function (res) { 46 callback(res.authSetting[scope] === true); 47 } 48 }); 49 }, 50 _showAuthorize: function (scope, callback) { 51 var me = this; 52 wx.authorize({ 53 scope: scope, 54 success: function () { 55 callback(); 56 }, 57 fail: function (res) { 58 if (scope === AuthorizeManager.scopes.userInfo) { 59 callback(); 60 } 61 else { 62 me._openAuthorizeSetting(scope, callback); 63 } 64 } 65 }) 66 }, 67 _openAuthorizeSetting: function (scope, calback) { 68 var me = this; 69 weixin.alert('提示', '您需要授权才能继续操作', function () { 70 wx.openSetting({ 71 success: function (res) { 72 if (!res.authSetting[scope]) { 73 me._openAuthorizeSetting(scope, callback); 74 } else { 75 callback && callback(); 76 } 77 } 78 }) 79 }); 80 } 81 }; 82 83 module.exports = AuthorizeManager;
三、结语
wframe会持续更新,我们会持续将项目中的最佳实践、框架优化等添加进来。
使用wframe框架开发小程序,那才能真正的体会JS面向对象的编程体验,这种体验是相当的美妙。希望小程序官方可以尽早引入wframe的设计思想,让小程序开发体验变成完完全全的面向对象开发体验。
THE END.