Original: https://pantao.parcmg.com/pre...
When making React Native applications, if you need to embed H5 pages in App, H5 and App can communicate in real time through the PostMessage function of Webview. However, in the applet, although a WebView component is also provided, when postMessage communication is made, it is in the official documentA very perverted description is given:
When a Web page postMessages to an applet, it triggers and receives a message at a specific time (applet backoff, component destruction, sharing).E.detail = {data}, data is an array of parameters for multiple postMessages
It is clear that no matter how many times we post Message from H5 page, the applet will not receive it unless:
- The user did a fallback to the previous page
- Component Destroy
- User clicked on Share
Actually, I'm not completely right about that. What the government said is applet retreat, not user retreat. After my actual measurement, it is really clear that people have expressed it. The retreat initiated by WeChat official SDK is also completely feasible:
wx.miniProgram.navigateBack()
General idea
From the analysis and measurements above, we can see that in order to achieve communication that can be accomplished without user action, the third scenario is one that we don't need to consider at all, so consider scenarios 1 and 2 carefully.
Method 1: Backward
When we want to send data to the applet through a web page and go back to the previous page, we can call wx.miniProgram.navigateBack() once after wx.miniProgram.postMessage, and the applet operates as follows:
- Processing postMessage information
- Back to the previous page
We do some special things when working with postMessage to save this data
Second way: component destruction
This is the most appropriate way for me to get the data for the applet while keeping it on the current page. It only needs to destroy the webview once. The general process is:
- Applet postMessage
- Applet navigateTo directs applet pages to a special page
- The special page of the applet immediately falls back to the page where the webview is located
- In the onShow of the page where the WebView is located, do a process, destroy the webview, and open it again
- Trigger onMessage to get data
- H5 page opened again
Although this method is abnormal, it can at least get the data in real time and keep it on the current H5 page. The only problem to solve is that the H5 page needs to be cached well before doing this whole set of operations, otherwise, the data of H5 will be emptied after opening it again.
One way: by backing out, data is submitted to the applet and passed to the previous page of the webview
It's a simple way to do that. Now we'll create two new pages:
sandbox/canvas-by-webapp/index.js
const app = getApp(); Page({ data: { url: '', dimension: null, mime: '', }, handleSaveTap: function() { wx.navigateTo({ url: '/apps/browser/index', events: { receiveData: data => { console.log('receiveData from web browser: ', data); if (typeof data === 'object') { const { url, mime, dimension } = data; if (url && mime && dimension) { this.setData({ url, dimension, mime, }); this.save(data); } } } } }) }, save: async function({ url, mime, dimension }) { try { await app.saveImages([url]); app.toast('Save successfully!'); } catch (error) { console.log(error); app.toast(error.message || error); } }, });
The core of the above code is the events parameter inside the wx.navigateTo call, which is used to communicate with/apps/browser/index pages and receive data.
apps/browser/index.js
I omitted the vast majority of code that is not relevant to this article and saved the three most important ones:
Page({ onLoad() { if (this.getOpenerEventChannel) { this.eventChannel = this.getOpenerEventChannel(); } }, handleMessage: 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)) { const messages = data.map(item => { try { const object = JSON.parse(item); this.handleMessage(object); return object; } catch (error) { return item; } }); this.setData({ messages: [...messages], }); } }, })
In fact, the onLoad method uses the getOpenerEventChannel method, which has been available since the WeChat SDK version 2.7.3, to create an event communication channel with the previous page, which we will use in handleMessage.
HandlePostMessage is the way that bindmessage is put on the Webview to handle messages coming from postMessage on H5 page. Because the applet sends messages from multiple postMessages together, the difference with other Webviews is that we get an array: e.detail..data, the purpose of handlePostMessage is to iterate through the array, take out each message, and hand it over to the handleMessage.
handleMessage takes out the message.action and message.data after it gets the message object (* Note here that this is a data structure that we designed in H5 and you can design your own structure in your own project). Actions do different things, and I'm working on it hereYes, when action =='postData', the message channel this.eventChannel obtained from getOpenerEventChannel pushes the data to the previous page, that is, /sandbox/canvas-by-webapp, but you do not need to execute navigateBack yourself, because this needs to be handed over to the H5 page to execute.
Implementation of H5 Page
My H5 main purpose is to use the html2canvas library to generate Canvas diagrams (I can't help but it's too cumbersome to draw in the applet), but instead of this discussion, we'll just generate the canvas pictures, convert them to base64 text, and do the following:
wx.miniProgram.postMessage({ data: JSON.stringify({ action: 'postData', data: 'BASE 64 IMAGE STRING' }) }); wx.miniProgram.navigateBack()
Immediately after postMessage, navigateBack() triggers a fallback, which also triggers the bindmessage event.
Real-time communication using destroyed webview
Next, I'll start this article with the focus, a more abnormal way, but I didn't think of a better way, so you'll have to communicate.
H5 Page Change
wx.miniProgram.postMessage({ data: JSON.stringify({ action: 'postData', data: 'BASE 64 IMAGE STRING' }) }); wx.miniProgram.navigateTo('/apps/browser/placeholder');
The H5 page simply changes wx.miniProgram.navigateBack() to wx.miniProgram.navigateTo('/apps/browser/placeholder'), and everything else is handled by the applet first.
/apps/browser/placeholder
The function of this page is really simple. When you open it, do a little bit of work and immediately go back to the previous page (that is, the page where the webview is located).
Page({ data: { loading: true }, onLoad(options) { const pages = getCurrentPages(); const webviewPage = pages[pages.length - 2]; webviewPage.setData( { shouldReattachWebview: true }, () => { app.wechat.navigateBack(); } ); }, });
Let's look at it line by line:
const pages = getCurrentPages();
This takes you to the page stack of the current whole applet. Since this page only allows you to come from the WebView page of the applet, its previous page must be the page where the WebView is located:
const webviewPage = pages[pages.length - 2];
Once you get the webviewPage page object, call its method setData to update a value:
webviewPage.setData( { shouldReattachWebview: true }, () => { app.wechat.navigateBack(); } );
When the shouldReattachWebview value is true, it means you need to attach the webview again. The event for this page has now been completed and you are back to the page where the webview is located.
apps/browser/index.js page
I also only keep the core code, specific logic, and I write it directly into the code.
Page({ data: { shouldReattachWebview: false, // Do you need to attach the webview component again webviewReattached: false, // Has the webview been attach ed once hideWebview: false // Whether to hide webview components }, onShow() { // If the webview needs to be re attach ed if (this.data.shouldReattachWebview) { this.setData( { // Hide webview hideWebview: true, }, () => { this.setData( { // Show it immediately after hiding, then complete a webview destruction and get the data in the postMessage hideWebview: false, webviewReattached: true, }, () => { // Once you get the data, process canvasData this.handleCanvasData(); } ); } ); } }, // This method is triggered when the webview is destroyed handlePostMessage: function(e) { const { data } = e.detail; if (Array.isArray(data)) { const messages = data.map(item => { try { const object = JSON.parse(item); this.handleMessage(object); return object; } catch (error) { return item; } }); this.setData({ messages: [...messages], }); } }, // Processing each message handleMessage: function(message) { const {action, data} = message // If saveCanvas action if (action === 'saveCanvas') { // Cache data into Snap first const { canvasData } = this.data; // app.checksum is my own encapsulation method, calculating the checksum of any data, and I use it as the key // This ensures that the same data will only be processed once const snapKey = app.checksum(data); // Only unprocessed data needs to be retrieved if (canvasData[snapKey] !== true) { if (canvasData[snapKey] === undefined) { // Cache data into `snap` // This is also my own way of encapsulating data that can be cached and read only once app.snap(snapKey, data); // Set the snapKey field in canvasData to `false` canvasData[snapKey] = false; this.setData({ canvasData, }); } } } }, // When the webview is re attach ed, the canvas data is saved in the snap. handleCanvasData: async function handleCanvasData() { const { canvasData } = this.data; // Get all the key s from canvasData and filter to the data you've already processed const keys = Object.keys(canvasData).filter(key => canvasData[key] === false); if (keys.length === 0) { return; } for (let i = 0; i < keys.length; i += 1) { try { const key = keys[i]; const { url } = app.snap(key); // Save the url (Base64 characters) to the album by encapsulating it yourself const saved = await app.saveImages(url); // Update canvasData object canvasData[key] = true this.setData({ canvasData }) console.log('saved: ', saved); } catch (error) { app.toast(error.message); return; } } }, })
The corresponding index.wxml file is as follows:
<web-view src="{{src}}" wx:if="{{src}}" bindmessage="handlePostMessage" wx:if="{{!hideWebview}}" />
Process Review and Summary
- Open webview page, open h5
- h5 page generates canvas diagram and converts to base64 character
- Send base64 to applet via wx.miniProgram.postMessage
- Call wx.miniProgram.navigateTo to direct the page to a special page
- On special pages, set shouldReattachWebview to true on the page where the webview is located
- Return to the page where the webview is located on a special page
- The onShow event on the page where the webview is located is triggered
- Detects whether shouldReattachWebview is true, if true, in the onShow event
- Setting hideWebview to true causes the destruction of web-view components
- handlePostMessage is triggered and handleMessage is handled step by step after all message s are resolved
- handleMessage finds an event with action =='saveCanvas', gets data
- Calculates the checksum from the data, caches the data with checksum as the key, and saves the checksum in the canvasData object
- At this point hideWebview is reset to false by setData in the callback of setData in onShow, web-view is re attach ed, H5 page is reloaded
- After the webview attach es again, this.handleCanvasData is triggered.
- handleCanvasData detects whether canvas data needs to be saved and, if so, saves and modifies the canvasData state
The whole process looks tedious, but it's good to write. The most important thing is to note that the data is heavy. WeChat's postMessage always gets all the data from H5 pages opened to closed.