2017年1月9日,咱们赢来了微信小程序,而在今年,小程序已经在彻底融入到咱们的生活中,能够说无处不在,赢来了一场真正的大爆发。微信之父张小龙在形容小程序时,是这样说的php
小程序是一种不须要下载安装便可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下便可打开应用。也体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。
而网友们在形容小程序的时候,是这么说的html
App就像原配,一年用不了几回;
服务号就像小三,每月固定用几回;
小程序就像炮友,用完即走;
那么在你点击启动一个小程序的时候,这个“p友”是如何作到无需安装卸载,触手可及,用完即走的呢,这其中在交互方面又发生了哪些不可告人的事情,让她如此特殊,下面以我最近接触到的一款小程序为例,简单总结一下小程序底层框架和一些api接口方面的设计思路。前端
你们都说小程序体验好,即开即用,比普通Webview的H5页面体验好不少,这个问题的我认为须要从几个方面考虑,首先呢,抛开业务层面的设计和优化,就是小程序底层框架的设计和实现方面的特色。ios
当咱们新建或打开一个小程序项目,以唱吧比赛小程序为例,便可看到以下图的项目结构。git
官方文档规定,入口文件app.js, 全局样式文件app.wxss,全局配置文件app.json,
每一个页面中再分视图wxml,wxss和逻辑js、文件配置json等,从这里咱们能够看出,整个小程序的上层框架,也就是大致分为视图层和逻辑层两个部分。
(官方文档https://developers.weixin.qq....)github
小程序采用的MINA框架,View层主要用来渲染页面结构,App Service层用来逻辑处理、数据请求、接口调用,它们在两个线程里运行,整个小程序是只有一个App Service的,而且整个小程序的生命周期内它是常驻内存的。View层主要使用WebView渲染,而App Service逻辑层是使用JSCore运行。ajax
通讯方面,View和AppService是双线程通讯的,主要经过系统层的JSBridage进行通讯,AppService把数据变化通知到View,表现方法也就是setData方法,触发View页面更新,View把触发的事件通知到AppService进行业务处理。json
这里要说的是,小程序是没有DOM结构的,那么视图层的渲染是如何作到的呢,就是运行环境中会把pages中的WXML的节点树结构,转化为JS的对象,进行渲染,这也是小程序体验优于普通分享页面的一大缘由,省去了不少关于浏览器DOM的操做,由JS运行环境之间进行渲染解析。axios
那么话说回来,此次在搭建唱吧小程序底层的时候,咱们其实作了哪些事情呢。首先,咱们并无进行纯Native层的搭建和改造,而是对上述提到的API层的一次的封装,尤为是在关于网络请求的改造和小程序启动的登陆流程方面,咱们前端团队尝试去作一些分层和优化。小程序
首先网络请求优化方面,微信提供的请求接口基本长这样:
wx.request({ url: 'test.php', //仅为示例,并不是真实的接口地址 data: { x: '' , y: '' }, header: { 'content-type': 'application/json' // 默认值 }, success: function(res) { console.log(res.data) } })
是否是感受和以前的某种请求模式很像,没错,就是古老的$.ajax,这时候咱们又想起了ajax的回调地狱,若是页面的请求不少,请求的顺序还有限定,瞬间又是各类嵌套,能够说是要从请求到懵逼了。
因此为了解决回调地狱和同时优化请求代码逻辑,咱们在封装wx.request的同时,咱们在小程序开发中,引入了async/await语法糖,用到了来自facebook的regenerator模块(详情请戳:https://github.com/facebook/r...),async、await函数经babel编译后,再用regenerator-runtime模块用于提供功能实现,这一方面也得力于小程序支持ES6语法的编译。
实现过程当中,单独用一个公共方法封装,返回wx.request的promise
//wechat.js const request = (url,options) => { return new Promise((resolve, reject) => { wx.request({ url: url, method: options.method, data: Object.assign({}, options.data), header: options.header, success: resolve, fail: reject }) }) }
以后在咱们的上层公共库中,编写与请求相关的处理逻辑。
// changba.js const regeneratorRuntime = require('./regenerator-runtime.js') const wechat = require('./wechat') const URI = 'xxx' const requestAPI = async (url,opt) => { const app = getApp() let options = Object.assign({data: {}},opt) if (/^\/api\/(.+)$/.test(url)) { url = URI + url; } if (!options.method) { options.method = 'POST'; } let header = { 'Content-Type': 'application/x-www-form-urlencoded' } options.header = options.header || header ; //除了login方法 其他接口都要加入sessionInfo也就是后端加密过的session_key if (!url.includes('/checkCode')) { options.data['sessionInfo'] = app.globalData.sessionkey; } let isTimeout = false; try { const ree = await wechat.checkSession(); } catch (error) { isTimeout = true; }; try { if (isTimeout) { let aaa = await login(app); } wx.showLoading({ title: '加载中' }); const res = await wechat.request(url,options) if (res && res.statusCode) { if (res.statusCode != 200) { if (wx.hideLoading) { wx.hideLoading() } wx.showModal({ content: wechat.errMsg(res.statusCode).message || '请求失败,请从新尝试', title: '提示', showCancel: false }) } else { if (res.data && res.data.code === 1) { if (wx.hideLoading) { wx.hideLoading() } return res.data } else { // xxx 其余状况业务逻辑处理 } } } } catch (error) { console.log('请求异常信息:' + error) if (wx.hideLoading) { wx.hideLoading() } wx.showModal({ content: '请求信息异常', title: '', showCancel: false }) } }
上述封装过程当中,因此除了考虑到请求超时、检查用户身份等操做,还能够加入session过时等相关其余的业务处理逻辑,这也是本身搭建请求的好处,针对本身的业务需求,进行匹配和改造。
然而在经历这样两层封装以后,在写业务逻辑代码的过程当中,就能够一目了然的发送请求了,达到逻辑清晰且书写自如,若是习惯了fetch以及axios的朋友应该都会比较喜欢这种方式。
async getdata() { let self = this; let cb_getdata = await app.changba.requestAPI('/api/xxx', { data: { id: self.data.id } }); if (cb_getdata && cb_getdata.code === 1) { // xxx } }
下面说下,启动小程序后的登陆流程方面,在这一方面,小程序与其余不一样的是,没有固定的登陆启动页面,而是彻底后台交互,固然根据产品定位和需求,也能够本身作一套登陆系统,可是微信官方给出的文档基本长这样:
没错,看着很头大,然而最头大的不是你循序渐进的实现了这个流程,而是实现了以后,你还会遇到不少意想不到的问题。
基本的流程不用多说,循序渐进便可,就是使用wx.login()能够得到开发者服务器向微信接口服务器请求得到session_key等数据时所须要的参数code,开发者服务器以code+appid+appsecret换取用户惟一标识openid和会话密钥session_key。但每一次调用wx.login()都会更新微信接口服务器上的session_key。
一样,改造微信api先,
// wechat 登陆封装 const login = () => { return new Promise((resolve, reject) => { wx.login({ success: resolve, fail: reject }) }) }
然后,在作本身的登陆封装时,能够先去请求微信的code,而后用在本身的请求中,获取并存储本身的登陆态。
let we_login = await wechat.login() // 微信登陆 let cb_login = await requestAPI('xxxx/checkCode', { data: {code: we_login.code}}) if (cb_login && cb_login.code === 1) { // xxxx 业务逻辑 } catch (error) { wx.showModal({ title: '登陆提示', content: '登陆失败', showCancel: false }) }
而在完成上面整套业务逻辑过程当中,可能会遇到一些意想不到的坑,这里面我印象比较深入的有两个,第一个是关于受权的问题,另外一个就是关于小程序生命周期与页面生命周期初始化过程当中异步请求回调顺序的问题。
先说第一个问题,关于受权框唤起的问题,也就是你常见的下面这个框。
只有用户受权后,才能够进一步获取用户的信息,这个框在最初是能够经过wx.getUserinfo()方法直接唤起,而在5月份之后,微信去掉了这个方法的功能,只能经过固定的button open-type去引导用户受权。
因此在底层逻辑的设计过程当中,就要抛弃以前login以后获取用户受权信息的设计思路,而是进行拆分,将login和受权的逻辑分开。
在必需要受权操做的地方例如咱们小程序中须要“参赛”或者“关注”的地方,进行单独受权的处理,经过使用wx.getSetting获取用户的受权状况
1) 若是用户已经受权,直接调用wx.getUserInfo获取用户最新的信息
2) 用户未受权,在界面中显示一个按钮提示用户登入,当用户点击并受权后就获取到用户的最新信息。
这个问题简单来讲,就是小程序启动有本身的生命周期onLaunch->onShow->onHide,而每一个page的实例化也有本身的生命周期,onLoad->onShow->onReady->onHide->onUnload
然而在开发过程当中,会遇到在App启动onLaunch的时候,发起登陆请求,并注册到咱们本身的服务器上以便使用,然而,这个过程当中,
app on launch -> request -> success -> page onload
是没法判断success和page onload哪一个先来的,会致使页面初始化数据失败的状况。
解决方案一
就是在request success中处理,使用getCurrentPages方法获取是否页面先于success生成,若是生成咱们就强制让页面再次渲染。
这显然是一种hack的方式, 在实际使用过程中,若是登陆逻辑比较复杂,这个方法不是十分便利,page onload在一些特殊状况也会被调用,这显然不是咱们想看到的
if (getCurrentPages().length != 0) { getCurrentPages()[getCurrentPages().length - 1].onLoad() }
解决方案二
目前我在开发中使用的是这种方案,
在login的逻辑里,增长一个回调函数cbLoginCallBack。
Page页面判断一下当前app.globalData.sessionKey是否存在,若是没有(第一次)则定义定义一个app方法(回调函数)
// Login Request if (app.cbLoginCallBack) { typeof app.cbLoginCallBack == 'function' && app.cbLoginCallBack(cb_login.data) } // 逻辑页面 if (app.globalData.sessionkey) { // init data } else { app.cbLoginCallBack = res => { if (res) { // init data } } }
App页面在请求success后判断时候有Page页面定义的回调方法,若是有就执行该方法。由于回调函数是在Page里面定义的因此方法做用域this是指向Page页面。
杂七杂八写了不少,基本都是近期开发和学习过程当中本身对小程序体验和交互方面的一点总结,在这里跟你们分享,以便遇到一样的问题,能够一块儿探讨,找出最优的解决方案,今年小程序的热度还将持续一阵,各大小公司持续发力,将来在这个领域还会有哪些事情发生,让咱们拭目以待。