原文:https://pantao.parcmg.com/pre...在做ReactNative应用的时候,如果需要在App中嵌入H5页面,那么通过Webview的PostMessage功能可以实现H5与App的实时通信,但是在小程序中,虽然也提供了一个webview组件,但是在进行postMessage通信的时候,官方文档给出了一个非常变态的解释:当网页postMessage到小程序,会在特定时间触发并接收消息(小程序的备份、组件销毁、分享)。e.detail={data},data是由多个postMessage参数组成的数组。这里已经说的很清楚了,无论我们从H5页面postMessage多少次,小程序都收不到,除非:用户为了返回到之前页面的操作组件,销毁用户点击分享。事实上,我并不完全正确。官方其实说的是小程序备份,并没有说用户在做备份操作。经过我的实测,确实如此。表达的很清楚,我们通过微信官方SDK调用的回滚也是完全可行的:wx.miniProgram.navigateBack()大致思路从上面的分析和实测可以知道,不需要用户操作就可以实现后通信完成后,我们根本不需要考虑第三种情况,我们再仔细考虑一下第一种和第二种情况。方法一:返回当我们想通过网页向小程序发送数据的同时返回到上一个页面,我们可以在wx.miniProgram.postMessage之后立即调用wx.miniProgram.navigateBack(),此时,小程序的操作是:处理postMessage信息,返回上一页。我们在处理postMessage的时候做了一些特殊的操作,这些数据就可以保存下来。第二种方法:组件销毁这个是最适合我的方法,可以让小程序在获取数据的同时保持在当前页面。它只需要销毁webview一次。大致流程是:小程序postMessage,小程序navigateTo,小程序页面到一个特殊的页面。页面立即返回webview所在页面的onShow,执行一个流程,销毁webview,然后再次打开触发onMessage获取数据。再次打开H5页面。这种方法很变态,但至少可以做到实时。获取数据并保留在当前H5页面。唯一需要解决的就是H5页面在做这整套操作之前需要缓存。否则再次打开后,H5数据会被清空。方法一:将数据提交给小程序,通过回滚的方式传递给webview的上一页。这个方法其实实现起来非常简单。我们现在创建两个新页面:sandbox/canvas-by-webapp/index.jsconstapp=getApp();Page({data:{url:'',dimension:null,mime:'',},handleSaveTap:function(){wx.navigateTo({url:'/apps/browser/index',events:{receiveData:data=>{console.log('从网络浏览器接收数据:',data);if(typeofdata==='object'){const{url,mime,dimension}=data;if(url&&mime&&dimension){this.setData({url,dimension,mime,});this.save(data);}}}}})},save:asyncfunction({url,mime,dimension}){try{awaitapp.saveImages([url]);app.toast('保存成功!');}catch(error){console.log(错误);app.toast(error.message||error);}},});上面代码中,核心点在于wx.navigat调用eTo时,里面的events参数用来和/apps/browser/index页面通信,apps/browser/index.js用来接收数据。我省略了大部分与本文无关的代码,保留了最重要的三个::function(message){const{action,data}=message;if(action==='postData'){if(this.eventChannel){this.eventChannel.emit('receiveData',data);}}},handlePostMessage:function(e){const{data}=e.detail;if(Array.isArray(data)){constmessages=data.map(item=>{try{constobject=JSON.parse(item);这个.handleMessage(object);returnobject;}catch(error){returnitem;}});this.setData({messages:[...messages],});}},})其实在onLoad方法,我们使用的是微信SDK2.7.3版本以来提供的getOpenerEventChannel方法,可以创建一个与之前页面的事件通信通道,我们会在handleMessage中使用到。handlePostMessage是通过bindmessage绑定到webview的方法。用于处理H5页面中postMessage的消息。由于小程序是一起发送多条postMessage消息,所以和其他Webview不同的是,我们拿到的是一个数组:e.detail.data,handlePostMessage的作用就是遍历这个数组,取出每条消息,然后交给handleMessage进行处理。handleMessage获取消息对象后,取出message.action和message.data(*这里要注意,这是我们在H5中设计的数据结构,大家可以在自己的项目中设计自己的结构),根据不同的操作到行动。我这里的处理是,当action==='postData'时,getOpenerEventChannel获取到的消息通道this.eventChannel会将数据推送到上层页面,也就是/sandbox/canvas-by-webapp,但是你不需要自己执行navigateBack,因为这个需要H5页面执行。H5页面的实现我的H5主要是使用html2canvas库生成Canvas图片(没办法,在小程序里画太麻烦了),不过这个不在本文的讨论过程中,我们将把它当作已经生成的画布图像,并将其转换为base64文本,并这样做:wx.miniProgram.postMessage({data:JSON.stringify({action:'postData',data:'BASE64图片字符串'})});wx.miniProgram.navigateBack()发送数据postMessage后,navigateBack()立即触发回滚,同时触发bindmessage事件。使用DestroyedWebview实现实时通信接下来开始本文的重点,是个变态的方式,但是没想到更好的方式,就直接交流吧。H5页面变化wx.miniProgram.postMessage({data:JSON.stringify({action:'postData',data:'BASE64IMAGESTRING'})});wx.miniProgram.navigateTo('/apps/browser/placeholder');在H5页面,只需将wx.miniProgram.navigateBack()改为wx.miniProgram.navigateTo('/apps/browser/placeholder'),其他的事情会先由小程序处理。/apps/browser/placeholder页面的功能其实很简单。打开后稍作操作,立马回到上一页(webview所在的页面。Page({data:{loading:true},onLoad(options){constpages=getCurrentPages();constwebviewPage=pages[pages.length-2];webviewPage.setData({shouldReattachWebview:true},()=>{app.wechat.navigateBack();});},});让我们一行一行看:constpages=getCurrentPages();这样就可以得到整个小程序的当前页面栈,因为这个页面只允许从小程序的Webview页面过来,所以它的上一页一定是webview所在的页面:constwebviewPage=pages[pages.length-2];得到webviewPage的page对象后,调用它的方法setData更新一个值:当shouldReattachWebview的值为true时,表示需要重新附加webview。现在这个页面的事件已经完成了,那么回到webview所在的页面apps/browser/index.js上,我也只保留核心代码,具体的逻辑我直接写到代码里了。Page({data:{shouldReattachWebview:false,//是否重新附加webview组件webviewReattached:false,//webview是否附加过一次hideWebview:false//是否隐藏webview组件},onShow(){//IfThewebviewneedstobereattachedif(this.data.shouldReattachWebview){this.setData({//隐藏webviewhideWebview:true,},()=>{this.setData({//隐藏后立即显示,完成一次Destroythewebview,获取postMessage中的数据hideWebview:false,webviewReattached:true,},()=>{//获取到数据后,处理canvasDatathis.handleCanvasData();});});}},//webview销毁时触发该方法handlePostMessage:function(e){const{data}=e.detail;if(Array.isArray(data)){constmessages=data.map(item=>{尝试{constobject=JSON.parse(item);this.handleMessage(对象);返回对象;}catch(错误){返回项目;}});this.setData({消息:[...消息],});}},//处理每条消息handleMessage:function(message){const{action,data}=message//ifsaveCanvasactionif(action==='saveCanvas'){//首先在Snap中缓存数据const{canvasData}=这个.数据;//应用程序。checksum是我自己封装的方法,用来计算任何数据的校验和,我用它作为key//这样可以保证同一条数据只处理一次constsnapKey=app.checksum(data);//只要没处理过if(canvasData[snapKey]!==true){if(canvasData[snapKey]===undefined){//把缓存中的数据放入`snap`//这是还有我自己封装的一个方法,可以缓存数据,只能读取一次app.snap(snapKey,data);//将canvasData中的snapKey字段设置为`false`canvasData[snapKey]=false;this.setData({canvasData,});}}}},//重新附加webview时,canvas数据已经保存到snap中,handleCanvasData:异步函数handleCanvasData(){const{canvasData}=this.data;//从canvasData中获取所有的key,过滤到处理后的数据constkeys=Object.keys(canvasData).filter(key=>canvasData[key]===false);如果(键。长度===0){返回;}for(leti=0;i
