微信单页应用的那些事
简介
这里介绍了一次在微信WebView中使用Vue做单页应用的过程中遇到的一些问题,也是比较常见的问题。文末有亮点,希望大家都来~
背景
最近因为快过年了,按照微信以往的尿性,小程序的审核总是有各种条条框框,因此为了让活动在线上正常进行且进行版本的迭代,(主要是为了能得到多一些的开发时间)我们打算采用h5来做活动。
目录
- 微信小程序跳转WebView的问题;
wx.config
进行初始化比较优雅的书写方式;- 微信签名的一些问题;
- Vue单页应用在微信浏览器内遇到的一些问题(包含微信支付);
- 单页应用内的通信;
微信小程序跳转WebView的问题
首先,我们先来聊一聊微信跳转到WebView的一些事情,在此之前大家可以先来看看官方给的文档:web-view · 小程序
普通的跳转没什么好说的,先说说我遇到的问题:
在小程序内进入WebView,我需要把小程序中storage模拟的cookie给带过去,调研了很多资料只得到一个方案,想要从小程序向WebView进行通信的话只能通过拼接URL,那很绝望啊,那个cookie有多长你知道吗😂?
解决方案:
其实也比较简单,我们在小程序进入WebView之前会对cookie进行一次操作,将cookie进行md5得到一个较短的字符串,通过这个md5进行拼接得到较短的URL会减少很多的问题。
注意点:
- 我们写在URL里边的地址需要encodeURIComponent一下,避免在链接中带有中文字符,否则在 iOS 中打开页面会有白屏的问题。
- WebView中的openid和小程序中的openid是不一样的,因此各位如果需要做用户关联的话最好用unionid。
- 跳转的WebView地址需要在mp后台里边进行加白名单,不然无法访问,域名要求是https的。
- 我们可以通过微信开发者工具里边公众平台进行开发,你可以在里边调试WebView的页面,jssdk的报错也可以在调试工具中体现,最后没有问题再通过代理的方式到手机上过一遍,这里我用的是Charles进行代理的。
wx.config比较优雅的书写方式
项目在引用JS-SDK的API的时候,必须先注入配置信息,也就是wx.config
,同一个页面(同一个URL)只需要初始化一次就OK了。wx.config
代码如下:
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: '', // 必填,公众号的唯一标识
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名
jsApiList: [] // 必填,需要使用的JS接口列表
});
具体的信息大家可以看一下文档,这里有一点需要特别提醒大家,写代码的时候,一定要严格按照微信文档里要求去写,大小写,数据类型都要与文档保持一致。微信公众平台
这些都是‘苦口婆心’的话,你要不信,可以试试╮(╯_╰)╭
我在写代码的时候喜欢把不同的功能写在不同的方法里边,初始化的函数只做初始化的事情,这样感觉比较舒服。
- 在所有微信接口调用前必须保证引入JS-SDK文件,设置JS接口安全域名。
- JS-SDK里有用户触发时才调用的API,有一点需要注意一下,
wx.config
是一个客户端的异步操作,在使用方法的时候会存在因异步导致的时机问题,所以方法最好写在wx.ready
中,wx.config
信息验证成功后会执行wx.ready
方法。 - 我们最好在初始化的时候习惯性的写上
wx.error
与wx.ready
同级,这样之后方法出现问题调试起来也会方便不少,也可以做报错提示。
上边的几个点没问题以后,我来说一下比较优雅的初始化方法,大概逻辑如下:
首先我们需要在方法外边封装一个Promise的模板,但是我的项目中也用了Jquery,我就直接用了里边的$.Deferred()
方法
let defer = $.Deferred();
let ready = defer.promise();
$.Deferred()
其实就是用来返回一个链式实用对象方法来注册多个回调,里边有多个方法
写好这两个方法以后基本上就完成一半了,激不激动!
按照上边介绍的逻辑,ready
是初始化成功,那么回调就是resolve
。error
是初始化失败,回调就是reject
。然后我们用defer.promise()
去接收两个状态并返回promise对象,之后你就可以愉快的then()
了。注意:deferred.promise()
也可以接受一个 target 参数,此时传入的 target 将被赋予 Promise 的方法,并作为结果返回,而不是创建一个新对象。
// Existing object
var obj = {
hello: function( name ) {
alert( "Hello " + name );
}
},
// Create a Deferred
defer = $.Deferred();
// Set object as a promise
defer.promise( obj );
// Resolve the deferred
defer.resolve( "John" );
// Use the object as a Promise
obj.done(function( name ) {
obj.hello( name ); // Will alert "Hello John"
}).hello( "Karl" ); // Will alert "Hello Karl"
具体的你可以看看官网的介绍
这一切都准备完了,接下来我们开始初始化配置
getWxSign(){
wx.config({
debug: false,
appId: ,
timestamp: ,
nonceStr: ,
signature: ,
jsApiList: [],
});
wx.error((err) => {
defer.reject(err);
});
wx.ready(() => {
defer.resolve();
});
}
这两步都完成以后,我们的链式解构就完成了,可以放飞自我的书写。
比如我想要获取微信的收货地址,就可以这么写
ready.then(()=>{
wx.openAddress({
success: function (res) {},
fail: function(err) {},
cancel: function() {}
});
});
通过这么一封装,你的代码的可读性就会有一个很大提升,至少看着很舒服(自我感觉)。
当然在使用这些jssdk的API的时候大家要记得加一个🔒,不然会有多次调用的情况,可能会出现返回好几次的情况,当然这个我没有试过,大家可以zuo一下。
微信签名的一些问题
关于微信签名失效的问题,这里主要是想提一下,因为这一块服务端的代码是另外一个小伙伴写的,所以我并没有太多的实践,微信签名服务端最好缓存一下access_token。这样可以避免很多不必要的麻烦。
官方是这么说的:公众平台以access_token为接口调用凭据,来调用接口,所有接口的调用需要先获取access_token,access_token在2小时内有效(7200s),过期需要重新获取,但1天内获取次数有限,开发者需自行存储。
其实在开发之初,服务端的同学还没写完这一块逻辑的时候,前端的同学可以这么去调试,先让服务端获取一次配置信息,然后前端暂时先把这一块写死,这样最少两个小时之内是没问题的,可以提高开发的效率。
还有一点,在缓存的有效期内提前去刷新新access_token,这个让服务端的同学统一控制,不要各自刷新,这样会出现冲突的情况,错误体现为invalid signature。确认这类错的时候我们需要充分利用官方给的签名校验工具
如果是因为token过期导致的签名失败,基本上在输入token以后就直接提示了,这个时候就要看看是不是服务端的缓存时间出了问题,当时我们遇到问题的原因是服务端底层关于时间的计算多*60…
当然出现签名错误还有许多的问题,在确保签名算法没有问题的情况下,可能会如下几种情况:
- 年少不知微信虎,参数大小写没有按照官方要求书写。重点需要注意一下nonceStr,timestamp两个字段。timestamp的数据类型是String,这个也要注意下。
- 确认URL是页面完整的url。可以通过
location.href.split('#')[0]
确认,目录的话只要填写都按上一级即可。
假如地址为:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
那么你的URL可以写到上一级
https://mp.weixin.qq.com/debug/cgi-bin
- config 与获取ticket的AppID不一致。
- 还有就是服务端如果没有缓存access_token,我们在调试的时候多刷几下可能就会出现问题了,因为开发的时候我感觉刷个几百上千还是很有可能的。
- 前端传递的URL可能会encodeURIComponent处理过的,服务端需要decode。
- 还有一个服务端获取的URL与前端的URL不一致,这个在手机上打开以后你可以复制一下链接检查一下。
vue单页应用在微信浏览器内遇到的一些问题
因为这回我们活动用的是vue的单页应用去完成的,路由用vue-router的history模式,因此会遇到一个比较烦躁的问题,具体的问题我描述一下场景:
在iOS上商品详情页跳转到订单详情页面进行支付,支付页面需要调起两个方法,而我的wx.config是mounted的时候就初始化的,所以每次进入这个页面都会报一个invalid signature,我就纳闷了,无意间发现刷新了一下页面就正常了。有时候不报这个签名问题地址也能使用,但是每次支付的时候他就报地址不对。最后我上网查了半天我就发现,history模式下,签名使用的URL是刚进入页面时的URL,为了确认是不是这个问题我把每一页的URL都贴出来,还真是都一样,既然问题确认了,那就好解决了。
解决思路:
在进入页面的时候我们可以先检测一下手机的类型,前边也说了我是在iOS的系统上遇到的问题,在安卓上就没问题,所以无需要对iOS专门处理一下,只要是识别到iOS的机型,那么我就把地址给改了,代码可以参考如下:
// 专门兼容iOS上微信签名的问题
beforeRouteEnter(to, from, next) {
var isiOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
if (isiOS && to.path !== location.pathname) {
location.assign(to.fullPath)
} else {
next();
}
},
当然,解决的办法不止这一个,其实有很多的解决方案,就是感觉其实不太优雅,当时其实我的备用方案就是要么用a标签进行页面跳转,或者进入这个页面以后,你可以reload
一下来更新URL。选择怎样的方法大家自己选择吧。
单页应用内的通信
首先我说一下场景大家方便理解一下目的。当时在做项目的时候整个项目只有一块功能用到这通信,当时服务端的童鞋让我不要在表单页面进行提交,因为内容不多,让我把信息带到订单页面结算时一起提交,这个其实就是非父子组件之间的通信,但是这个需要通信的地方没那么多,引入一个vuex感觉太重了,我就直接不考虑这个方案了,就然这个不用,那就直接用eventBus其实也可以,代码如下:
首先我们需要声明一个eventBus的js文件:
import Vue from 'vue';
var eventBus = new Vue({});
export default eventBus;
接下来你需要传递你的值:
import eventBus from 'eventBus/eventBus.js';
let info = {
a: '',
b: ''
};
eventBus.$emit('isVal',info);
我们在提交表单的页面加一个emit
的事件,把填写的内容传出去,接下来我们就要跳回到订单页面了。
import eventBus from 'eventBus/eventBus.js';
eventBus.$on('isVal',(data)=>{
//....
});
使用on
进行接收,这样就完成了,其实也是比较简单的逻辑,没涉及到太多东西,对了这个监听可以放在mounted
或者create
里边,建议用once
就OK了。
我们开发的小程序叫做一番市集,欢迎大家注册体验,可以加微信zyf348519452联系我要邀请码哦。