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. function Application() {
  6. this.initUrl = '';
  7. this.host = '';
  8. this.session = null;
  9. this.initialized = false;
  10. this.mock = false;
  11. this.useDefaultConfigsOnInitFailed = false;
  12. this.authorizeManager = new AuthorizeManager();
  13. this._userInfo = null;
  14. this._readyHandlers = [];
  15. };
  16.  
  17. Application.prototype = {
  18. onLaunch: function () {
  19. var me = this;
  20. if(this.initUrl === ''){
  21. throw 'please create YourOwnApplication class in app.js that inerits from Application class and provide initUrl in constructor';
  22. }
  23. var client = new WebClient();
  24. client.post(this.initUrl, null, function(result){
  25. if (result.success || me.useDefaultConfigsOnInitFailed){
  26. me.initialized = true;
  27. me.onInitialized(result.success ? result.value : null);
  28. me.triggerReady();
  29. }
  30. else{
  31. weixin.alert('小程序初始化失败', result.message);
  32. }
  33. }, '初始化中...');
  34. },
  35. onShow: function () {
  36.  
  37. },
  38. onHide: function () {
  39.  
  40. },
  41. onError: function () {
  42.  
  43. },
  44. onPageNotFound: function () {
  45.  
  46. },
  47. ready: function (callback) {
  48. var me = this;
  49. if (this.initialized === true) {
  50. callback && callback();
  51. return;
  52. }
  53. this._readyHandlers.push(callback);
  54. },
  55. triggerReady: function () {
  56. for (var i = 0; i < this._readyHandlers.length; i++) {
  57. var callback = this._readyHandlers[i];
  58. callback && callback();
  59. }
  60. this._readyHandlers = [];
  61. },
  62. onInitialized: function(configs){
  63.  
  64. },
  65. getUserInfo: function(callback){
  66. var me = this;
  67. if(this._userInfo != null){
  68. callback && callback(this._userInfo.userInfo);
  69. return;
  70. }
  71. this.authorizeManager.getUserInfo(function(result){
  72. me._userInfo = result;
  73. callback && callback(me._userInfo.userInfo);
  74. });
  75. },
  76. getCurrentPage: function(){
  77. var pages = getCurrentPages();
  78. return pages.length > 0 ? pages[0] : null;
  79. }
  80. };
  81.  
  82. 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. function AuthPage() {
  4. DemoPageBase.call(this, 'auth');
  5. this.requireLogin = false;
  6. };
  7.  
  8. AuthPage.prototype = new DemoPageBase();
  9.  
  10. AuthPage.prototype.onPreload = function (options) {
  11. this.returnUrl = decodeURIComponent(options.returnUrl);
  12. };
  13.  
  14. AuthPage.prototype.onGotUserInfo = function (event) {
  15. var me = this;
  16. if (event.detail.userInfo == null) {
  17. return;
  18. }
  19. var app = getApp();
  20. app._userInfo = event.detail;
  21. DemoPageBase.prototype.login.call(this, app._userInfo.userInfo, function () {
  22. me.go(me.returnUrl, false);
  23. })
  24. }
  25.  
  26. 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. 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. function AuthorizeManager() {
  4. this.pageUrl = '/pages/_authorize/_authorize';
  5. };
  6.  
  7. AuthorizeManager.scopes = {
  8. userInfo: 'scope.userInfo'
  9. };
  10.  
  11. AuthorizeManager.prototype = {
  12. authorize: function (scope, callback) {
  13. var me = this;
  14. me._isAuthorized(scope, function (authorized) {
  15. if (authorized) {
  16. callback();
  17. }
  18. else {
  19. me._showAuthorize(scope, callback);
  20. }
  21. });
  22. },
  23. getUserInfo: function (callback) {
  24. var me = this;
  25. var scope = AuthorizeManager.scopes.userInfo;
  26. function handleAuthorized() {
  27. wx.getUserInfo({
  28. success: function (res) {
  29. callback && callback(res);
  30. },
  31. fail: function (res) {
  32. var url = getApp().getCurrentPage().getFullUrl();
  33. wx.redirectTo({
  34. url: me.pageUrl + "?returnUrl=" + encodeURIComponent(url)
  35. });
  36. }
  37. })
  38. };
  39. me.authorize(scope, handleAuthorized);
  40. },
  41. _isAuthorized: function (scope, callback) {
  42. wx.getSetting({
  43. success: function (res) {
  44. callback(res.authSetting[scope] === true);
  45. }
  46. });
  47. },
  48. _showAuthorize: function (scope, callback) {
  49. var me = this;
  50. wx.authorize({
  51. scope: scope,
  52. success: function () {
  53. callback();
  54. },
  55. fail: function (res) {
  56. if (scope === AuthorizeManager.scopes.userInfo) {
  57. callback();
  58. }
  59. else {
  60. me._openAuthorizeSetting(scope, callback);
  61. }
  62. }
  63. })
  64. },
  65. _openAuthorizeSetting: function (scope, calback) {
  66. var me = this;
  67. weixin.alert('提示', '您需要授权才能继续操作', function () {
  68. wx.openSetting({
  69. success: function (res) {
  70. if (!res.authSetting[scope]) {
  71. me._openAuthorizeSetting(scope, callback);
  72. } else {
  73. callback && callback();
  74. }
  75. }
  76. })
  77. });
  78. }
  79. };
  80.  
  81. module.exports = AuthorizeManager;

三、结语

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

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

THE END.

[原创] 分享我们自己搭建的微信小程序开发框架——wframe及设计思想详解的更多相关文章

  1. 微信小程序 使用腾讯地图SDK详解及实现步骤

    信小程序 使用腾讯地图SDK详解及实现步骤    微信小程序JavaScript SDK: 官方文档:http://lbs.qq.com/qqmap_wx_jssdk/index.html 步骤: 1 ...

  2. 分享一下我的个人微信小程序

    分享一下我的个人微信小程序 1.有我平时整理的一些小程序相关的技术,供大家参考. 2.有几个好玩的例子 有问题可以一起参考

  3. 微信小程序答题,怎么设计页面渲染,答完一题,跳到下一题

    想要的效果 1.第一页只显示第一道题的内容,如图红框2.答题后,点击下一题,内容显示第二道题的内容 代码 answer.wxml <!--pages/answer/answer.wxml--&g ...

  4. 微信小程序开发框架技术选型

    目前微信小程序开发有三种方式,原生微信小程序,使用mpVue或wepy微信小程序开发框架. 三种开发方式横向对比资料如下:  

  5. 【猿分享第10期】微信小程序Meetup扫盲专场回顾(转载)

    首先感谢答疑师:子慕 前端工程师,目前就职于医联,偶尔写点博客,吐槽总结,偶尔吟“湿”作对,润滑万物,江湖人称子慕大诗人. 直播间语音回放收听,请微信扫描下图二维码授权进入即可. 以下为本次直播的全部 ...

  6. 原创:新手布局福音!微信小程序使用flex的一些基础样式属性

    来源:新手布局福音!微信小程序使用flex的一些基础样式属性 作者:Nazi   Flex布局相对于以前我们经常所用到的布局方式要好的很多,在做微信小程序的时候要既能符合微信小程序的文档开发要求,又能 ...

  7. 微信公众号菜单添加小程序,miniprogram,pagepath参数详解,php开发公众号

    随着微信小程序功能的开发, 已经可以跟公众号打通了, 主要有两种方式: 1) 在公众号文章中插入小程序 2) 在公众号菜单中添加小程序 第一种方式, 子恒老师在前面的课程已经详细介绍过, 今天来讲第二 ...

  8. 微信小程序开发框架整理

    目前除了原生的微信小程序开发外,各大厂商陆续造了自己的开发框架,现整理如下: WePY 腾讯官方开源的小程序组件化开发框架,目前有15K+Star ,一直在更新着,社区活跃,掉坑能快速的找到方法爬出来 ...

  9. 【原创】1、简单理解微信小程序

    先看下网站的运行方式: 而小程序是这样: what?就这样?是的,就这样.那小程序官方提供的Wafer,还有Wafer2...想太多了,抛弃它们吧.不应当为了解决一个简单的旧问题而去整一个复杂的新问题 ...

随机推荐

  1. Windows上使用Thunderbird与GPG发送和解密公钥加密的电子邮件

    作者:荒原之梦 原文链接:http://zhaokaifeng.com/?p=552 非对称加密的原理: 最先出现的加密方法是对称加密.在对称加密算法中是不区分公钥和私钥的,加密与解密使用的都是同一个 ...

  2. ROC曲线的计算

    1.ROC曲线简介 在评价分类模型时,会用到ROC(receiver operating characteristic)曲线.ROC曲线可用来评价二元分类器( binary classifier)的优 ...

  3. FPGA学习笔记(四)——Verilog基本语法

    ###### [该随笔部分内容转载自小梅哥] ######### 组合逻辑:    多路选择器.加法器.译码器.乘法器 时序逻辑:    计数器.分频器.定时器.移位寄存器 一.Verilog文件的基 ...

  4. Python读取文件时输入文件绝对路径报错

    问题描述 读取本地文件,输入绝对路径时出错,显示数据被截断. 最开始以为是转译错误,因为eclipse会自动把\变成\\, pycharm这里没有,就手动改一下.结果还是不行. 根本原因 文件名出错了 ...

  5. JFree图表

    ************************************************************************************* 使用JFree生成统计图片, ...

  6. Python + Appium 【已解决】driver(session)在多个class之间复用,执行完一个类的用例,再次执行下个类的用例时不需要初始化

    实现效果:打开App进行自动化测试,只需打开APP一次,按先后顺序执行n个py文件中的相应操作,实现自动化测试. 示例:如截图示例,一个App,根据此APP内不同的模块,写成了不同的py文件, 预期结 ...

  7. 计算机17-3,4作业C

    C.Class Degisn Description 定义一个Circle类,有成员变量(或称之为域)x,y(圆心坐标)r(圆半径),成员方法intersect()两个圆是否相交的判断方法,和所需要的 ...

  8. java IO流全面总结

    流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作. Ja ...

  9. Redis+Restful 构造序列号和压力测试【后续】

    大家还记上篇博文https://www.cnblogs.com/itshare/p/8643508.html,测试redis构造流水号的tps是600多/1s. 这个速度显然不能体现redis 集群在 ...

  10. Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)

    ========================================================          ================================== ...