知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏壧峁┍憬莸闹С郑?
您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >
復(fù)雜場景下的h5與小程序通信
發(fā)表時(shí)間:2021-1-6
發(fā)布人:葵宇科技
瀏覽次數(shù):66
一、背景
在套殼小程序盛行的當(dāng)下, h5調(diào)用小程序能力來打破業(yè)務(wù)邊界已成為家常便飯,h5與小程序的結(jié)合,極大地拓展了h5的能力邊界,豐富了h5的功能。使許多以往純h5只能想想或者實(shí)現(xiàn)難度極大的功能變得輕松簡單。
但在套殼小程序中,h5與小程序通信存在以下幾個(gè)問題:
- 注入小程序全局變量的時(shí)機(jī)不確定,可能調(diào)用的時(shí)候不存在小程序變量。和全局變量my相關(guān)的判斷滿天飛,每個(gè)使用的地方都需要判斷是否已注入變量,否則就要創(chuàng)建監(jiān)聽。
- 小程序處理后的返回結(jié)果可能有多種,h5需要在具體使用時(shí)監(jiān)聽多個(gè)結(jié)果進(jìn)行處理。
- 一旦監(jiān)聽建立,就無法取消,在組件銷毀時(shí)如果沒有判斷組件狀態(tài)容易導(dǎo)致內(nèi)存泄漏。
二、在業(yè)務(wù)內(nèi)的實(shí)踐
-
因業(yè)務(wù)的特殊性,需要投放多端,小程序sdk的加載沒有放到head里面,而是在應(yīng)用啟動時(shí)動態(tài)判斷是小程序環(huán)境時(shí)自動注入的方式:
export function injectMiniAppScript() { if (isAlipayMiniApp() || isAlipayMiniAppWebIDE()) { const s = document.createElement('script'); s.src = http://www.wxapp-union.com/'https://appx/web-view.min.js'; s.onload = () => { // 加載完成時(shí)觸發(fā)自定義事件 const customEvent = new CustomEvent('myLoad', { detail:'' }); document.dispatchEvent(customEvent); }; s.onerror = (e) => { // 加載失敗時(shí)上傳日志 uploadLog({ tip: `INJECT_MINIAPP_SCRIPT_ERROR`, }); }; document.body.insertBefore(s, document.body.firstChild); } }
加載腳本完成后,我們就可以調(diào)用
my.postMessage
和my.onMessage
進(jìn)行通信(統(tǒng)一約定h5發(fā)送消息給小程序時(shí),必須帶action
,小程序根據(jù)action
處理業(yè)務(wù)邏輯,同時(shí)小程序處理完成的結(jié)果必須帶type
,h5在不同的業(yè)務(wù)場景下通過my.onMessage
處理不同type的響應(yīng)),比如典型的,h5調(diào)用小程序簽到:
h5部分代碼如下:// 處理掃臉簽到邏輯 const faceVerify = (): Promise<AlipaySignResult> => { return new Promise((resolve) => { const handle = () => { window.my.onMessage = (result: AlipaySignResult) => { if (result.type === 'FACE_VERIFY_TIMEOUT' || result.type === 'DO_SIGN' || result.type === 'FACE_VERIFY' || result.type === 'LOCATION' || result.type === 'LOCATION_UNBELIEVABLE' || result.type === 'NOT_IN_ALIPAY') { resolve(result); } }; window.my.postMessage({ action: SIGN_CONSTANT.FACE_VERIFY, activityId: id, userId: user.userId }); }; if (window.my) { handle(); } else { // 先記錄錯(cuò)誤日志 sendErrors('/threehours.3hours-errors.NO_MY_VARIABLE', { msg: '變量不存在' }); // 監(jiān)聽load事件 document.addEventListener('myLoad', handle); } }); };
實(shí)際上還是相當(dāng)繁瑣的,使用時(shí)都要先判斷my是否存在,進(jìn)行不同的處理,一兩處還好,多了就受不了了,而且這種散亂的代碼遍布各處,甚至是不同的應(yīng)用,于是,我封裝了下面這個(gè)sdk
miniAppBus
,先來看看怎么用,還是上面的場景// 處理掃臉簽到邏輯 const faceVerify = (): Promise<AlipaySignResult> => { miniAppBus.postMessage({ action: SIGN_CONSTANT.FACE_VERIFY, activityId: id, userId: user.userId }); return miniAppBus.subscribeAsync<AlipaySignResult>([ 'FACE_VERIFY_TIMEOUT', 'DO_SIGN', 'FACE_VERIFY', 'LOCATION', 'LOCATION_UNBELIEVABLE', 'NOT_IN_ALIPAY', ]) };
可以看到,無論是postMessage還是監(jiān)聽message,都不需要再關(guān)注環(huán)境,直接使用即可。在業(yè)務(wù)場景復(fù)雜的情況下,提效尤為明顯。
三、實(shí)現(xiàn)及背后的思考
-
為了滿足不同場景和使用的方便,公開暴露的interface如下:
interface MiniAppEventBus { /** * @description 回調(diào)函數(shù)訂閱單個(gè)、或多個(gè)type * @template T * @param {(string | string[])} type * @param {MiniAppMessageSubscriber<T>} callback * @memberof MiniAppEventBus */ subscribe<T extends unknown = {}>(type: string | string[], callback: MiniAppMessageSubscriber<T>): void; /** * @description Promise 訂閱單個(gè)、或多個(gè)type * @template T * @param {(string | string[])} type * @returns {Promise<MiniAppMessage<T>>} * @memberof MiniAppEventBus */ subscribeAsync<T extends {} = MiniAppMessageBase>(type: string | string[]): Promise<MiniAppMessage<T>>; /** * @description 取消訂閱單個(gè)、或多個(gè)type * @param {(string | string[])} type * @returns {Promise<void>} * @memberof MiniAppEventBus */ unSubscribe(type: string | string[]): Promise<void>; /** * @description postMessage替代,無需關(guān)注環(huán)境變量 * @param {MessageToMiniApp} msg * @returns {Promise<unknown>} * @memberof MiniAppEventBus */ postMessage(msg: MessageToMiniApp): Promise<unknown>; }
subscribe
:函數(shù)接收兩個(gè)參數(shù),
type:需要訂閱的type,可以是字符串,也可以是數(shù)組。
callback:回調(diào)函數(shù)。subscribeAsync
:接收type(同上),返回Promise對象,值得注意的是,目前只要監(jiān)聽到其中一個(gè)type返回,promise就resolved,未來對同一個(gè)action對應(yīng)多個(gè)結(jié)果type時(shí)存在問題,需要拓展,不過目前還未遇到此類場景。unsubscribe
:取消訂閱。postMessage
:postMessage替代,無需關(guān)注環(huán)境變量。完整代碼:
import { injectMiniAppScript } from './tools'; /** * @description 小程序返回結(jié)果 * @export * @interface MiniAppMessage */ interface MiniAppMessageBase { type: string; } type MiniAppMessage<T extends unknown = {}> = MiniAppMessageBase & { [P in keyof T]: T[P] } /** * @description 小程序接收消息 * @export * @interface MessageToMiniApp */ export interface MessageToMiniApp { action: string; [x: string]: unknown } interface MiniAppMessageSubscriber<T extends unknown = {}> { (params: MiniAppMessage<T>): void } interface MiniAppEventBus { /** * @description 回調(diào)函數(shù)訂閱單個(gè)、或多個(gè)type * @template T * @param {(string | string[])} type * @param {MiniAppMessageSubscriber<T>} callback * @memberof MiniAppEventBus */ subscribe<T extends unknown = {}>(type: string | string[], callback: MiniAppMessageSubscriber<T>): void; /** * @description Promise 訂閱單個(gè)、或多個(gè)type * @template T * @param {(string | string[])} type * @returns {Promise<MiniAppMessage<T>>} * @memberof MiniAppEventBus */ subscribeAsync<T extends {} = MiniAppMessageBase>(type: string | string[]): Promise<MiniAppMessage<T>>; /** * @description 取消訂閱單個(gè)、或多個(gè)type * @param {(string | string[])} type * @returns {Promise<void>} * @memberof MiniAppEventBus */ unSubscribe(type: string | string[]): Promise<void>; /** * @description postMessage替代,無需關(guān)注環(huán)境變量 * @param {MessageToMiniApp} msg * @returns {Promise<unknown>} * @memberof MiniAppEventBus */ postMessage(msg: MessageToMiniApp): Promise<unknown>; } class MiniAppEventBus implements MiniAppEventBus{ /** * @description: 監(jiān)聽函數(shù) * @type {Map<string, MiniAppMessageSubscriber[]>} * @memberof MiniAppEventBus */ listeners: Map<string, MiniAppMessageSubscriber[]>; constructor() { this.listeners = new Map<string, Array<MiniAppMessageSubscriber<unknown>>>(); this.init(); } /** * @description 初始化 * @private * @memberof MiniAppEventBus */ private init() { if (!window.my) { // 引入腳本 injectMiniAppScript(); } this.startListen(); } /** * @description 保證my變量存在的時(shí)候執(zhí)行函數(shù)func * @private * @param {Function} func * @returns * @memberof MiniAppEventBus */ private async ensureEnv(func: Function) { return new Promise((resolve) => { const promiseResolve = () => { resolve(func.call(this)); }; // 全局變量 if (window.my) { promiseResolve(); } document.addEventListener('myLoad', promiseResolve); }); } /** * @description 監(jiān)聽小程序消息 * @private * @memberof MiniAppEventBus */ private listen() { window.my.onMessage = (msg: MiniAppMessage<unknown>) => { this.dispatch<unknown>(msg.type, msg); }; } private async startListen() { return this.ensureEnv(this.listen); } /** * @description 發(fā)送消息,必須包含action * @param {MessageToMiniApp} msg * @returns * @memberof MiniAppEventBus */ public postMessage(msg: MessageToMiniApp) { return new Promise((resolve) => { const realPost = () => { resolve(window.my.postMessage(msg)); }; resolve(this.ensureEnv(realPost)); }); } /** * @description 訂閱消息,支持單個(gè)或多個(gè) * @template T * @param {(string|string[])} type * @param {MiniAppMessageSubscriber<T>} callback * @returns * @memberof MiniAppEventBus */ public subscribe<T extends unknown = {}>(type: string | string[], callback: MiniAppMessageSubscriber<T>) { const subscribeSingleAction = (type: string, cb: MiniAppMessageSubscriber<T>) => { let listeners = this.listeners.get(type) || []; listeners.push(cb); this.listeners.set(type, listeners); }; this.forEach(type,(type:string)=>subscribeSingleAction(type,callback)); } private forEach(type:string | string[],cb:(type:string)=>void){ if (typeof type === 'string') { return cb(type); } for (const key in type) { if (Object.prototype.hasOwnProperty.call(type, key)) { const element = type[key]; cb(element); } } } /** * @description 異步訂閱 * @template T * @param {(string|string[])} type * @returns {Promise<MiniAppMessage<T>>} * @memberof MiniAppEventBus */ public async subscribeAsync<T extends {} = MiniAppMessageBase>(type: string | string[]): Promise<MiniAppMessage<T>> { return new Promise((resolve, _reject) => { this.subscribe<T>(type, resolve); }); } /** * @description 觸發(fā)事件 * @param {string} type * @param {MiniAppMessage} msg * @memberof MiniAppEventBus */ public async dispatch<T = {}>(type: string, msg: MiniAppMessage<T>) { let listeners = this.listeners.get(type) || []; listeners.map(i => { if (typeof i === 'function') { i(msg); } }); } public async unSubscribe(type:string | string[]){ const unsubscribeSingle = (type: string) => { this.listeners.set(type, []); }; this.forEach(type,(type:string)=>unsubscribeSingle(type)); } } export default new MiniAppEventBus();
class內(nèi)部處理了腳本加載,變量判斷,消息訂閱一系列邏輯,使用時(shí)不再關(guān)注。
四、小程序內(nèi)部的處理
-
定義action handle,通過策略模式解耦:
const actionHandles = { async FACE_VERIFY(){}, async GET_STEP(){}, async UPLOAD_HASH(){}, async GET_AUTH_CODE(){}, ...// 其他action } .... // 在webview的消息監(jiān)聽函數(shù)中 async startProcess(e) { const data = http://www.wxapp-union.com/e.detail; // 根據(jù)不同的action調(diào)用不同的handle處理 const handle = actionHandles[data.action]; if (handle) { return actionHandles[data.action](this, data) } return uploadLogsExtend({ tip: STRING_CONTANT.UNKNOWN_ACTIONS, data }) }
使用起來也是得心順暢,舒服。
其他
類型完備,使用時(shí)智能提示,方便快捷。
相關(guān)案例查看更多
相關(guān)閱讀
- 軟件定制公司
- 網(wǎng)站建設(shè)招商
- 云南網(wǎng)站建設(shè)首選公司
- 報(bào)廢車管理
- 制作一個(gè)小程序
- 云南網(wǎng)站建設(shè)電話
- 區(qū)塊鏈
- 網(wǎng)站建設(shè)方法
- 云南網(wǎng)站建設(shè)公司排名
- 云南小程序代建
- 云南網(wǎng)站建設(shè)方法
- 百度小程序公司
- 小程序用戶登錄
- .net網(wǎng)站
- 英文網(wǎng)站建設(shè)公司
- 云南小程序被騙蔣軍
- 網(wǎng)頁制作
- 網(wǎng)站上首頁
- 網(wǎng)站優(yōu)化公司
- 做網(wǎng)站
- 云南衛(wèi)視小程序
- 快排推廣
- painter
- 網(wǎng)站建設(shè)報(bào)價(jià)
- 昆明做網(wǎng)站建設(shè)的公司排名
- 云南網(wǎng)站建設(shè)高手
- 報(bào)廢車回收管理軟件
- 報(bào)廢車管理系統(tǒng)
- 昆明軟件定制
- web服務(wù)