[原创] 分享我们自己搭建的微信小程序开发框架——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;
View Code

 

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;
View Code

 

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;
View Code

 

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;
View Code

 

 三、结语

 wframe会持续更新,我们会持续将项目中的最佳实践、框架优化等添加进来。

 

使用wframe框架开发小程序,那才能真正的体会JS面向对象的编程体验,这种体验是相当的美妙。希望小程序官方可以尽早引入wframe的设计思想,让小程序开发体验变成完完全全的面向对象开发体验。

 

THE END.

posted @ 2018-06-08 11:25  Leo C.W  Views(9442)  Comments(12Edit  收藏  举报