大家好,我是疯狂的小波。
小程序的 web-view
,并不支持小程序向嵌入网页的主动通信,比如小程序调用嵌入网页内部的方法、让嵌入网页刷新等。
这篇文章主要是向大家介绍,怎么通过我们自己的方式,实现小程序与web-view
嵌入网页的主动通信;以及网页怎么向小程序发送消息。
背景
最近在开发小程序的过程中,有个需求。在小程序的A
页面,通过web-view
嵌入一个H5
,H5
中有个任务组件,任务组件有完成状态;这里有2个要求:
- 如果任务是未完成状态,点击去完成按钮跳转到小程序原生
B
页面,做指定操作后,就视为完成任务,再返回到A
页面,此时H5
需要重新请求接口,获取最新的任务状态是否完成。 A
页面使用小程序的分享时,分享自定义内容需要使用H5
页面接口返回数据
流程大概是这个样子:
我们先来看第1点要求:返回A页面时,H5重新请求接口。由于H5
本身并没有页面显示的这种监听方法,那我们就只能通过小程序来通知H5
更新。比如:小程序A
页面onShow
时,调用H5
的更新方法。这也是我们要实现这个需求的实现方案:小程序主动更新H5。
小程序主动更新H5
查看官方文档,我们可以发现:小程序向H5
的通信,只支持嵌入网页链接设置、获取H5
发送的消息、H5
加载成功/失败的监听;并没有提供小程序直接调用H5
内部方法等直接通信的api。
所以我们这里想要更新,只能利用嵌入网页链接的设置这一特性。
链接地址更新及问题
比如要更新页面时,更新我们嵌入的H5
地址,如:在地址后添加时间戳来达到页面刷新的效果。
// 小程序A页面
onShow() {
const t = new Date().getTime();
this.setData({
viewUrl: `https://www.baidu.com?t=${t}`
});
}
可以发现,此时页面会正常刷新。但是这种更新有2个问题:
- 会产生一条新的浏览历史记录:此时小程序返回上一页时,返回的是地址更新前的上一个baidu页面
- 传统的网页可以正常刷新,但是嵌入的网页如果是单页应用则不会更新
我们可以思考下,为什么会出现这2个问题,以及怎么解决?
这是因为修改web-view
的src
属性,相当于在嵌入的网页中做了一次跳转,跳转地址就是我们修改后地址。
所以第一个问题,会产生一条新的浏览历史记录,就很好理解了。
第二个问题,在单页应用中(如Vue),这种跳转相同路由,不同参数的场景,vue-router
发现这是同一个组件,会复用这个组件,所以不会进行重新渲染。
单页应用
那我们拿嵌入的页面是Vue来举例,我们现在的问题就变成了:
- 相同路由更新参数时,如何实现页面刷新?
- 地址变更后,会产生新的浏览记录导致返回时没有返回目标页,怎么处理?
相同路由的跳转更新,我们可以使用watch
监听$route
的变更,再执行我们想要的更新操作。产生新的历史记录,我们可以在路由变更时手动回退,不影响返回操作,需要注意的是手动回退时也会触发路由变更,但是不需要执行更新。
相同路由的跳转更新,除了监听路由,也有其他的方案。可以参考这篇文章的第2条
完整代码如下:
// web-view嵌入的H5页面(Vue)
watch: {
$route: function() {
// 如果是手动返回变更hash,不触发重新获取页面数据
if (this.isBackChange) {
this.isBackChange = false
return
}
// 执行我们想要的操作,如:调用接口重新获取页面数据
this.initData()
// 执行返回操作,小程序webview变更参数后
// 会使webview历史栈+1。需要手动清除该记录
setTimeout(() => {
// 标识是否手动返回,不触发接口请求
this.isBackChange = true
this.$router.go(-1)
})
}
}
通过上面的方法,我们就能够实现在小程序中更新地址,主动更新H5
的这一效果。并且不会产生额外的历史记录,小程序的页面栈也能够正常返回。
传统网页
上面我们通过baidu的例子,通过更改参数,实现了页面更新,但是这种更新是整体页面的刷新,有时并不能满足我们的需求,并且体验也不是很好。那传统网页下,我们怎么实现单页应用这种无感更新呢?
这里我们首先可以思考下,上面Vue的实现原理是什么?
其实就是监听路由的变化,然后执行更新。所以我们可以先简单看看,Vue实现监听$route
的原理。
vue-router
有2种路由模式。
hash:
url
中带 #
,#
后面的值通常就是我们常说的hash
值,而hash
值只是客户端的一种状态,向服务器发送请求时,hash
部分并不会被发送。hash
变更时,再通过 window.onhashchange
监听hash
的变化,根据不同的hash
值展示不同的内容(路由),或者hash
变化时,执行变化回调(路由监听)。
history:
url
中不带 #
,利用 History API
来实现URL
的变化,操作浏览器的历史记录,通过 history.pushState
新增一条记录,或者使用history.replaceState
替换当前的历史记录。执行该API后,url
地址会立即替换但是不会刷新页面。histoty
模式使用onpopstate
监听浏览记录的回退。
而我们在小程序中改变web-view
地址,就可以使用更改hash
值的方式。代码如下:
// 小程序A页面
onShow() {
const t = new Date().getTime();
this.setData({
viewUrl: `https://www.baidu.com#${t}`
});
}
// web-view嵌入的H5页面(传统网页)
let isBackChange = false;
window.addEventListener('hashchange', data => {
// 如果是手动返回变更hash,不触发重新获取页面数据
if (isBackChange) {
isBackChange = false
return
}
// 执行我们想要的操作,如:调用接口重新获取页面数据
initData()
// 执行返回操作,小程序webview变更参数后
// 会使webview历史栈+1。需要手动清除该记录
setTimeout(() => {
// 标识是否手动返回,不触发接口请求
isBackChange = true
window.history.go(-1)
})
})
可以看到,这里除了监听的方式不一样,整体的处理逻辑与效果是一致的。
小结
到这里,我们就实现了小程序与 web-view
嵌入网页的主动通信。在监听路由变化的时候,除了可以做页面更新,还可以做其他更多的事情。比如根据不同的路由参数变化,执行不同的操作等,完全可以自定义执行事件,实现主动通信。
小程序使用H5数据进行分享
接下里,我们来看看第二个需求:小程序分享时,要使用嵌入的网页数据。
要实现这个效果,就需要网页主动向小程序发送消息。这个比较简单,直接上代码。
// web-view嵌入页面
var wx = require('weixin-js-sdk')
// 接口获取数据 ...
const data = {
shareDesc,
shareImage
}
// 向小程序发送消息,传递接口获取的分享标题和图片
wx.miniProgram.postMessage({ data })
// 小程序A页面
<web-view src="{{viewUrl}}" bindmessage="bindPostMessage"></web-view>
// 页面内方法
// 监听h5发送的postMessage消息
bindPostMessage(e) {
// 发起分享时,获取h5发送的分享内容
// 这里接收到的数据是一个数组,所以如果有多个数据发送,也可以添加一个type区分
const shareObj = e.detail.data[0]
this.shareObj = {
title: shareObj.shareDesc,
imageUrl: shareObj.shareImage
};
}
// 分享页面,获取h5传递过来的自定义分享参数
onShareAppMessage() {
return this.shareObj;
}
这样就可以轻松实现分享 H5
的数据了。
需要注意的是,小程序只有在特定的场景才会触发接收消息的事件(小程序后退、组件销毁、分享),也就是 bindPostMessage
事件的触发,并不是在 h5
调用 postMessage
时触发,而是在小程序后退、组件销毁、分享时。上面的例子中,可以理解为,小程序发起分享时,先执行了 bindPostMessage
函数,再执行 onShareAppMessage
。
总结
上面我们介绍了小程序与 web-view
嵌入 H5
的双向通信。
小程序与 H5
的主动通信,可以通过变更 web-view
的 src
地址,然后在 H5
中监听地址的变更,重新请求接口获取最新数据,同时需要处理浏览历史记录新增的问题。在单页应用和传统网页中处理方式有点不一样,但是原理是差不多的。
H5
也可以主动向小程序发送消息,但是小程序只有在特定的场景才会触发接收消息的事件(小程序后退、组件销毁、分享),接收到的数据是多次发送的数据组成的数组。