摘要
本文记录了菜鸟作者在使用uniapp开发项目时,遇到的众多问题其二:实现开发微信小程序的webview与h5页面的间接实时通信。
吐槽:不愧是大厂啊,就是这么财大气粗有个性。
前言
开发工具: uniapp 3.1.22;微信小程序开发者工具 Stable 1.05.2107090
研发背景:在开发支付宝小程序时,发现webview和H5是可以实时通讯的,因此h5页面在开发时使用了不少这种接口。但是,当开发微信小程序时,惊讶的发现它竟然不支持实时通信,不愧是大厂,就是这么傲娇。
正文
一、h5发送消息到webview
因为到目前为止,微信小程序的wx.miniProgram.postMessage()方法还不能实时通信,而只能在特定时机(小程序后退、组件销毁、分享)触发组件的message事件,所以要实现这个目标还要另寻出路。
有没有方法呢?
当然,还是有方法的。废话哈,否则就不会有这篇博客了。我在网络上就看到过别人给出的解决方案。这里就直接拿过来用了。这是一个绕远路的方式,先用官方api中的wx.miniProgram.navigateTo()方法,从A页面中的H5页面(命名为a)打开第二个页面B,然后在B页面通过const pages = getCurrentPages()方法来拿到A页面的实例,从而调用A页面中的方法,同时关闭页面B。
(一)具体代码如下:
1、页面A: 通过web-view标签打开一个H5页面a
<template>
<view>
<web-view :src="src" @message="getMessage" ></web-view>
</view>
</template>
<script>
export default {
data() {
return {
src: '',
};
},
onLoad: function(option) {
this.src = "http://127.0.0.1:8080/#/a";
},
methods: {
getMessag(){
},
callback({ // 接收页面B回传的参数
response,
method
}){
console.log("callback.response: ", response); // 显示 {name: "张三"}
console.log("callback.method: ", method); // 显示 passToA
},
}
};
</script>
<style>
</style>
2、H5页面a: 这里只做一件事,打开新页面B
function passToWebView(){
wx.miniProgram.navigateTo({
url: "/pages/wxWebView/B?name=张三"
});
}
3、页面B: 接受参数,然后转发给页面A
<template>
<view>
通信中...
</view>
</template>
<script>
export default {
data(){
return {
prevPageResponse: {}
};
},
onLoad(option) {
this.prevPageResponse = option;
wx.navigateBack(); // 立刻关闭页面
},
onUnload(){ // 关闭页面时调用的生命周期
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2]; // 上一个页面,也就是页面A
if (prevPage.$vm.callback != undefined) {
prevPage.$vm.callback({
response: this.prevPageResponse,
method: "passToA"
});
}
}
}
</script>
<style>
</style>
(二)弊端
这么做会有一个弊端,就是会把页面B显示出来,这里没有办法,只能这么做。如果有大佬有更好的方法,请不吝赐教。
二、webview发送消息到h5
想要用官方api实现,几乎是不可能的,菜鸟作者并没有找到这种方法,网上貌似也没有,因为我没找到。
但是,经过我再三的尝试,发现了一种实现方式,通过h5监听hashchange事件来实现!使用这种解决方式的前提是url必须是hashmode,如果不是也可以根据这种思路,摸索出类似的解决方式。
重要的事情说三遍:这不是官方方法!这不是官方方法!这不是官方方法!
所以,存在一定的风险。将来如果鹅厂抽风了,把这种方式给更新掉了,我们也只能xxxxx。不过,到时候可能还会有新的解决方式也不一定。万一哪天鹅厂大佬想通了,把实时通信放开了呢。
不过,考虑最坏的情况出现的话,还可以通过后台提供websocket或者轮询等服务,来解决这个通信问题。
(一)代码实现
1、页面A-更新: 在callback方法接收到回传参数后,直接调用通信h5方法
<template>
<view>
<web-view :src="src" @message="getMessage" ></web-view>
</view>
</template>
<script>
export default {
data() {
return {
src: '',
backupSrc: ''
};
},
onLoad: function(option) {
this.src = "http://127.0.0.1:8080/#/a";
this.backupSrc = this.src; // 保存原始地址,方便多次通信时,参数不会被污染。
},
methods: {
getMessag(){
},
callback({ // 接收页面B回传的参数
response,
method
}){
console.log("callback.response: ", response); // 显示 {name: "张三"}
console.log("callback.method: ", method); // 显示 passToA
let src = this.backupSrc;
if (src.indexOf("?") <= -1) {
src += "?";
} else {
src += "&";
}
src += "__callback=1&__callbackResponse=" + JSON.stringify(response);
if (method) {
src += "&__callbackMethod=" + method;
}
src += "&" + Math.random(); // 当需要多次通信时,避免src没有变化导致hashchange失效
this.src = src; // 通过修改src,来让hashchange监听事件执行
},
}
};
</script>
<style>
</style>
2、h5页面a-更新: 更新passToWebView方法,实现接收返回参数
let hashchange = (method) => {
return new Promise((resolve, reject) => {
// 微信小程序在webview的src被修改过的情况下,会在关闭页面时,
// 触发其他页面的postmessage,导致连锁反应从而触发其他页面的hashchange
window.addEventListener('hashchange', function(event){
let callback = JSUtil.getQueryVariable("__callback");
let callbackMethod = JSUtil.getQueryVariable("__callbackMethod");
let callbackResponse = JSUtil.getQueryVariable("__callbackResponse");
if (JSUtil.isNotEmpty(callback)) {
if (callbackMethod == method
|| (JSUtil.isEmpty(callbackMethod) && JSUtil.isEmpty(method))) {
if (callbackResponse && typeof callbackResponse == 'string' && callbackResponse.indexOf("{") == 0) {
callbackResponse = JSON.parse(decodeURIComponent(callbackResponse));
}
if (callback == 1) {
resolve(callbackResponse);
} else {
reject(callbackResponse);
}
}
}
});
});
}
function passToWebView(){
wx.miniProgram.navigateTo({
url: "/pages/wxWebView/B?name=张三"
});
hashchange("passToA").then((res) => {
// 这里只是做个示范,把参数返回了,实际开发中,可以在小程序页面完成一些操作后,把操作结果传进来
console.log(res); // 显示 {name: "张三"}
});
}
(二)优点
这么做有一个优点,不知道大家发现没有。就是传参时,不会把参数暴露到网络上,只会在客户内部传递,除非微信的webview本身有漏洞,否者一般不会有暴露的风险。这样就可以传递一些比较敏感的信息,比如token、秘钥等等。这也是我为什么废了这么多时间要实现这种传递参数的方式的原因。
三、采坑
1、webview标签的src路径千万不要出现“//”的路径,否则页面会不停刷新。
2、hashchange接收的callbackResponse是自动编码过的,必须用decodeURIComponent进行解码
四、后记
如果有官方的方法,当然更好,哪位大佬还有更好的方法的话,还请多多指教。
这里再重复一遍,考虑出现最坏的情况,这种方式不能用了的时候,还可以通过后台提供websocket或者轮询等服务,来解决这个通信问题。
有时候,解决问题的方法,往往出人意料又在知识体系之内,不试试还真不知道能有什么方式来解决困难。生活也是如此,需要勇敢一点,需要有点实现理想的勇气,万一实现了呢?