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

  • 相关阅读:
    Windows DLL调用实例
    DLL头文件的格式和应用
    Strategy factory
    抽象数据类型(ADT)和面向对象编程(OOP)3.5 ADT和OOP中的等价性
    抽象数据类型(ADT)和面向对象编程(OOP)3.4 面向对象的编程
    抽象数据类型(ADT)和面向对象编程(OOP)3.3 抽象数据类型
    抽象数据类型(ADT)和面向对象编程(OOP)3.2规约
    抽象数据类型(ADT)和面向对象编程(OOP)3.1数据类型和类型检查
    软件构造 消息传递
    软件构造 并发3(线程安全性)----锁定和同步
  • 原文地址:https://www.cnblogs.com/leotsai/p/wframe-complete-documentation.html
Copyright © 2020-2023  润新知