前天同事問我公司內(nèi)部的小程序怎么對接的,我回憶了一下,簡單記錄了一下前端同學(xué)需要注意的點(diǎn)。
背后還有小程序架構(gòu)、網(wǎng)絡(luò)策略等等。當(dāng)時(shí)恰逢小程序架構(gòu)調(diào)整,(老架構(gòu)的時(shí)候我就發(fā)現(xiàn)了有一個(gè)問題點(diǎn)可以優(yōu)化,但是跟那邊人反饋之后,人家表示不要我管??,新架構(gòu)時(shí)發(fā)現(xiàn)這個(gè)問題還巧妙的遺留下來了??)我雖然不負(fù)責(zé)那塊,但是本著這樣不優(yōu)雅的原則,還是跟新架構(gòu)的對接人講了我的優(yōu)化方案,講明白了之后,同時(shí)上報(bào)各自直系領(lǐng)導(dǎo),并建議我領(lǐng)導(dǎo)牽頭開會推動。最后,無奈存量數(shù)據(jù)太多,老架構(gòu)那邊權(quán)衡之后決定不改動。(多說了幾句,權(quán)當(dāng)記錄一下)
1、背景
公司研發(fā)的一款服務(wù)軟件App(姑且稱為“大地”),提供了包涵消息、待辦、工作臺、同事圈和通訊錄五大功能模塊,其中,工作臺里集成了包括公司的移動客戶端、PC端以及第三方平臺的部分功能/服務(wù)(統(tǒng)稱為“應(yīng)用”)。
我今天要講的是這個(gè)集成平臺以什么方式展現(xiàn)“應(yīng)用”,答案是:借鑒了微信的架構(gòu),自研了“小程序”接入“應(yīng)用”。
我司小程序具有一種相對開放能力(面向全公司),賦能業(yè)務(wù)快速數(shù)字化、場景敏捷迭代,并且可在“大地”上便捷的獲取和使用,同時(shí)具有完善的使用體驗(yàn)(這就是嚴(yán)格的接入審核標(biāo)準(zhǔn)帶來的好處)。
在“大地”開發(fā)者平臺,創(chuàng)建小程序會自動創(chuàng)建配套的公眾號(公眾號是為了推送消息使用,可訂閱)。小程序開發(fā)不限制技術(shù)選型,開發(fā)完成之后按照小程序接入規(guī)范打包上架小程序,審核發(fā)布。
簡單來說,可以把“大地”看成是一個(gè)“釘釘”,我現(xiàn)在要把我們的業(yè)務(wù)功能投放到“大地”上,就需要接入“大地”小程序,以小程序的方式在“大地”上為用戶提供服務(wù)。
小程序架構(gòu):Cordova框架做的WebView,運(yùn)行我開發(fā)的前端程序,通過Nginx幫我把請求代理到微服務(wù)網(wǎng)關(guān),由網(wǎng)關(guān)轉(zhuǎn)發(fā)到目的主機(jī)處理請求。它雖然看上去是一個(gè)Native App,但只有一個(gè)UI WebView,里面訪問的是一個(gè)Web App,對我來說就是開發(fā)一個(gè)H5應(yīng)用調(diào)用一些所需的JSBridge,也就是所謂的Hybrid App。
下面看一下本地開發(fā)中的一些問題,以及我是怎么處理的
2、問題
Hybrid App本地開發(fā)過程中沒有真實(shí)的Native環(huán)境的,同樣也無法使用JSBridge,這就會帶來一個(gè)問題:跟原生交互的行為只能發(fā)布小程序才可以調(diào)試,本地玩不了,這...,相當(dāng)fuck。
目的是想讓本地開發(fā)同小程序測試環(huán)境具有相同的體驗(yàn),我的想法是在本地模擬JSBridge的方法,盡管不能帶來真實(shí)的效果,至少觸發(fā)了某個(gè)行為之后要有個(gè)反應(yīng),不至于讓操作流程看起來像是“脫節(jié)”的(實(shí)際跟原生的交互行為并不多,比如:拍照、彈窗提示、定位等等)。
因此,我要做的就是本地模擬JSBridge的一些方法,開發(fā)時(shí)觸發(fā)了這些原生交互行為之后提示一些信息,等到上架小程序測試環(huán)境時(shí),在手機(jī)上會用真實(shí)的JSBridge方法自動替換掉我模擬實(shí)現(xiàn)的方法。
于是我就開始了下面的準(zhǔn)備工作。
搞清楚
JSBridge運(yùn)行的原理本地模擬
JSBridge的方法上架小程序是自動使用真實(shí)的
JSBridge
3、了解JSBridge
JSBridge:望文生義就是js和Native之前的橋梁,而實(shí)際上JSBridge確實(shí)是JS和Native之前的一種通信方式。
簡單的說,JSBridge就是定義Native和JS的通信,Native只通過一個(gè)固定的橋?qū)ο笳{(diào)用JS,JS也只通過固定的橋?qū)ο笳{(diào)用Native。JSBridge另一個(gè)叫法及大家熟知的Hybrid app技術(shù)。

了解即可,更多的請參考
下圖展示了JSBridge的工作流程??

上圖中左側(cè)部分正式我要做的,具體請看下文
看累了,三連一下,回看不迷路喲??
3.1、我們的JSBridge
推測“大地”那邊的JSBridge應(yīng)該是自己寫的,沒有初始化JSBridge的操作
當(dāng)調(diào)用JSBridge時(shí),必須在頁面完全加載完成之后才能夠拿到全局的JSBridge,Cordova框架提供deviceready事件,該事件觸發(fā)的時(shí)候表示全局的JSBridge掛載成功。(注意:這就是我接下來操作的切入點(diǎn),嘻嘻)
簡單寫下如下:
document.addEventListener('deviceready', function () {
console.log('deviceready OK!');
JSAPI.showToast(0, '提示信息')
}, false)
需要注意的是,在開發(fā)環(huán)境,是沒有 deviceready 事件的,所以上面的代碼并不會執(zhí)行,只有在app里面運(yùn)行的時(shí)候才會執(zhí)行。
思考:
JSBridge必須是在deviceready事件觸發(fā)后方能使用的,因此首先要做的就是自定義deviceready事件,本地環(huán)境可以在load事件里觸發(fā)自定義deviceready事件,生產(chǎn)環(huán)境下監(jiān)聽deviceready事件即可

4、JS發(fā)起自定義事件
我是用 CustomEvent 構(gòu)造函數(shù),繼承至 Event,文檔看這里
- 用法
new CustomEvent(eventName, params);
- 示例
創(chuàng)建一個(gè)自定義事件
const event=new CustomEvent('mock-event');
- 傳遞參數(shù)
這里值得注意,需要把想要傳遞的參數(shù)包裹在一個(gè)包含detail屬性的對象,否則傳遞的參數(shù)不會被掛載
function createEvent(params, eventName = 'mock-event') {
return new CustomEvent(eventName, { detail: params });
}
const event = createEvent({ id: '0010' });
- 發(fā)起事件
調(diào)用dispatchEvent方法發(fā)起事件,傳入你剛才創(chuàng)建的方法
window.dispatchEvent(event);
- 監(jiān)聽事件
window.addEventListener('mock-event', ({ detail: { id } }) => {
console.log('id',id) // 會在控制臺打印0010
});
- 示例:
document.body.addEventListener('show', (event) => { console.log(event.detail); });
// 觸發(fā)
let myEvent = new CustomEvent('show', {
detail: {
username: 'xixi',
userid: '2022'
}
});
document.body.dispatchEvent(myEvent);
了解了自定義事件之后,通過自定義事件模擬觸發(fā)
deviceready事件,這樣上面的deviceready事件監(jiān)聽就可以執(zhí)行了。注意:這里還要確定一個(gè)問題,在什么時(shí)候觸發(fā)自定義事件
deviceready呢?
5、確定 deviceready 事件執(zhí)行時(shí)機(jī)
- 只需要編寫如下代碼,查看輸出結(jié)果即可
window.addEventListener('load', function () {
console.log('load OK!');
}, false);
document.addEventListener('deviceready', function () {
console.log('deviceready OK!');
}, false);
- 結(jié)果輸出
load OK!
deviceready OK!
由此可知,執(zhí)行順序:load --> deviceready
6、自定義事件模擬Cordova deviceready事件
自定義
deviceready事件根據(jù)上面測試執(zhí)行順序得出的結(jié)論,我在
load事件里觸發(fā)自定義事件在開發(fā)環(huán)境下模擬一些用到的
JSBridge-API,比如下面寫到的JSAPI.showToast()方法
- mockEvent.js
if (process.env.NODE_ENV === 'development') {
// 自定義事件
let myEvent = new CustomEvent('deviceready');
// 模擬JSAPI事件
window['JSAPI'] = {
showToast(type, desc) {
console.log(type, desc);
}
}
// ...
// 開發(fā)環(huán)境下,在 原生 load 方法之后 觸發(fā)自定義事件
window.addEventListener('load', function () {
console.log('load OK!');
setTimeout(() => {
document.body.dispatchEvent(myEvent);
}, 100)
}, false);
}
7、封裝deviceReady方法
實(shí)現(xiàn)在Cordova框架觸發(fā)deviceready事件的時(shí)候感知到,以便于在deviceReady事件觸發(fā)后執(zhí)行JS-API。
可用于開發(fā)環(huán)境和非開發(fā)環(huán)境
7.1、方式一
這里采用鏈?zhǔn)秸{(diào)用的方式,
以下這種借助 Promise 的實(shí)現(xiàn),在這種場景下其實(shí)是不合理的??
只是形式上類似,其實(shí)并不是
- 定義
- mixin.js
deviceReady() {
return new Promise((resolve) => {
window.addEventListener('deviceready', function () {
resolve("ready go!");
}, false);
})
}
- 組件內(nèi)使用
JS-API
使用JSAPI可以如下這么寫
this.deviceReady().then((res) => {
console.log(res); // ready go!
JSAPI.showToast(0, '提示')
})
this.deviceReady().then((res) => {
JSAPI.getUserInfo((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
})
- 開發(fā)環(huán)境執(zhí)行效果如下

7.2、方式二(推薦)
改寫成通用的事件監(jiān)聽函數(shù),支持鏈?zhǔn)秸{(diào)用
開發(fā)環(huán)境下,由
mockEvent.js文件里的dispatchEvent觸發(fā)自定義的deviceready事件;小程序里運(yùn)行,則由真實(shí)的
deviceready事件觸發(fā)
- 定義
- mixin.js
receiver(type) {
let callbacks = {
fns: [],
then: function(cb){
this.fns.push(cb);
return this;
}
};
document.addEventListener(type, function(ev) {
let fns = callbacks.fns.slice();
for(let i = 0, l = fns.length; i < l; i++){
fns[i].call(this, ev);
}
});
return callbacks;
}
- 使用
this.receiver('deviceready').then((ev) => {
console.log(ev);
JSAPI.getUserInfo(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
)
})
this.receiver("click")
.then(() => console.log("hi"))
.then(()=> console.log(22));
最后
當(dāng)應(yīng)用發(fā)布到app上,就是監(jiān)聽的真實(shí)的 Cordova框架的 deviceready 事件了,之后也就可以拿到真實(shí)的JSAPI了,以上只是為了在開發(fā)環(huán)境的時(shí)候模擬使用JSAPI。防止在開發(fā)環(huán)境下直接調(diào)用JSAPI飄紅的情況,當(dāng)然也是可以加try catch處理的,只不過個(gè)人感覺模擬事件使得代碼看起來更加優(yōu)雅別致一點(diǎn),使用更加絲滑,酌情食用??。
軟件架構(gòu)非常有意思,感興趣的可以交流探索,嘻嘻。

我是 甜點(diǎn)cc
熱愛前端,也喜歡專研各種跟本職工作關(guān)系不大的技術(shù),技術(shù)、產(chǎn)品興趣廣泛且濃厚,等待著一個(gè)創(chuàng)業(yè)機(jī)會。主要致力于分享實(shí)用技術(shù)干貨,希望可以給一小部分人一些微小幫助。
我排斥“新人迷茫,老人看戲”的現(xiàn)象,希望能和大家一起努力破局。營造一個(gè)良好的技術(shù)氛圍,為了個(gè)人、為了我國的數(shù)字化轉(zhuǎn)型、互聯(lián)網(wǎng)物聯(lián)網(wǎng)技術(shù)、數(shù)字經(jīng)濟(jì)發(fā)展做一點(diǎn)點(diǎn)貢獻(xiàn)。數(shù)風(fēng)流人物還看中國、看今朝、看你我。