深入小程序系列之一:小程序核心原理及模擬
什么是小程序
小程序是一種新的移動應用程序格式,是一種依賴 Web 技術,但也集成了原生應用程序功能的混合解決方案。
目前市面上小程序平臺微信、支付寶、百度、頭條、京東、凡泰等;小程序一些特性有助于填補 Web 和原生平臺之間的鴻溝,因此小程序受到了一些超級應用程序的歡迎。
它不需要安裝,支持熱更新。
具備多個 Web 視圖以提高性能。
它提供了一些通過原生路徑訪問操作系統功能(原生接口)或數據的機制。
它的內容通常更值得信賴,因為應用程序需要由平臺驗證。
小程序可以分發到多個小程序平臺(Web、原生應用,甚至是 OS)。這些平臺還為小程序提供了入口,幫助用戶輕松找到所需的應用。
小程序核心功能
1、分離視圖層與邏輯層
在小程序中,視圖層通常與邏輯層分離。
視圖層 View 負責渲染小程序頁面,包括 Web 組件和原生組件渲染,可以將其視為混合渲染。例如,Web 組件渲染可以由 WebView 處理,但 WebView 不支持某些 Web 組件渲染,或者是性能受限;小程序還依賴于某些原生組件,例如地圖、視頻等。
邏輯層 Service 是用主要用于執行小程序的 JS 邏輯。主要負責小程序的事件處理、API 調用和生命周期管理。擴展的原生功能通常來自宿主原生應用程序或操作系統,這些功能包括拍照、位置、藍牙、網絡狀態、文件處理、掃描、電話等。它們通過某些 API 調用。當小程序調用原生 API 時,它會將 API 調用傳遞給擴展的原生功能,以便通過 JSBridge 進一步處理,并通過 JSBridge 從擴展的原生功能獲取結果。Service 為每個 Render 建立連接,傳輸需要渲染的數據以進一步處理。
如果事件由小程序頁面中的組件觸發,則此頁面將向 Service 發送事件以進一步處理。同時,頁面將等待 Service 發送的數據來重新渲染小程序頁面。
渲染過程可被視為無狀態,并且所有狀態都將存儲在 Service 中。
視圖層和邏輯層分離有很多好處:
方便多個小程序頁面之間的數據共享和交互。
在小程序的生命周期中具有相同的上下文可以為具備原生應用程序開發背景的開發人員提供熟悉的編碼體驗。
Service 和 View 的分離和并行實現可以防止 JS 執行影響或減慢頁面渲染,這有助于提高渲染性能。
因為 JS 在 Service 層執行,所以 JS 里面操作的 DOM 將不會對 View 層產生影響,所以小程序是不能操作 DOM 結構的,這也就使得小程序的性能比傳統的 H5 更好。
小程序雙線程模型模擬
先看一下運行結果
接下來我們將用 iOS 代碼來模擬上述的雙線程模型。首先我們來實現視圖層與邏輯層的數據通訊
如上圖所示,視圖層與邏輯層都分別通過 JS Bridge 的 publish 和 subscribe 來實現數據的收發。
模擬實現
1、視圖層調用JSBridge.publish把事件傳遞給原生;參數: {eventName: ‘’, data: {}}
//點擊按鈕,通知JS執行業務邏輯 function?onTest()?{ ??console.log('aaa') ??FinChatJSBridge.subscribe('PAGE_EVENT',?function?(params)?{ ????????????????????????????document.getElementById('testId').innerHTML?=?params.data.title????????????????????????????????}) ??FinChatJSBridge.publish('PAGE_EVENT',?{ ????eventName:?'onTest',data:?{} ??}) }
2、原生 view 層收到 page 的事件,把事件傳遞轉發給 service 層處理
if?([message.name?isEqualToString:@"publishHandler"])?{ ????????NSString?*e?=?message.body[@"event"]; ????????[self.service?callSubscribeHandlerWithEvent:e?param:message.body[@"paramsString"]]; ????}
3、原生 service 層收到原生 view 層的事件,通過 jsbridge 把事件及參數傳遞給視圖 ervice 層執行 js 邏輯
NSString?*js?=?[NSString?stringWithFormat:@"ServiceJSBridge.subscribeHandler('%@',%@)",eventName,jsonParam]; [self?evaluateJavaScript:js?completionHandler:nil];
4、視圖 service,收到事件后,執行 JS 業務代碼
var?Page?=?{ ??setData:?function(data)?{ ????//向原生視圖層發送更新數據信息 ????ServiceJSBridge.publish('PAGE_EVENT',?{ ??????eventName:?'onPageDataChange', ??????data:?data ????}) ??}, ??methods:?{ ????onTest:?function()?{ ??????//?執行JS方法,模擬小程序的setData,把數據更新到視圖層 ??????Page.setData({ ????????title:?'我來自JS代碼更新' ??????}) ??????console.log('my?on?Test') ????} ??} } var?onWebviewEvent?=?function(fn)?{ ??ServiceJSBridge.subscribe('PAGE_EVENT',?function(params)?{ ????console.log('FinChatJSBridge.subscribe') ????var?data?=?params.data, ??????eventName?=?params.eventName ????fn({ ??????data:?data, ??????eventName:?eventName ????}) ??}) } var?doWebviewEvent?=?function(pEvent,?params)?{ ??//?do?dom?ready ??if?(Page.methods.hasOwnProperty(pEvent))?{ ????//?收到視圖層的事件,執行JS對應的方法 ????Page.methods[pEvent].call(params) ??} }
5、執行業務 JS 代碼后,把數據更新傳遞給視圖層去更新 UI 界面展示數據
ServiceJSBridge.publish('PAGE_EVENT',{??? eventName:'onPageDataChange',? data:?data? })
6、原生 service 層收到視圖 service 層的事件,把事件傳遞給原生視圖層
if?([message.name?isEqualToString:@"publishHandler"])?{ ????NSString?*e?=?message.body[@"event"]; ????[self.controller?callSubscribeHandlerWithEvent:e?param:message.body[@"paramsString"]];????}
7、原生視圖層把收到的事件,傳遞給視圖 view 層
NSString?*js?=?[NSString?stringWithFormat:@"FinChatJSBridge.subscribeHandler('%@',%@)",eventName,jsonParam]; [self?evaluateJavaScript:js?completionHandler:nil];
8、視圖 view 層,收到事件后,更新界面
FinChatJSBridge.subscribe('PAGE_EVENT',function(params){? document.getElementById('testId').innerHTML?=?params.data.title? })
訂閱數據回調
訂閱數據回調 //?首先訂閱數據回調 JSBridge.subscribe('PAGE_EVENT',?function(params)?{ ??//?...?這里對返回的數據進行處理 }) //?向JS?Bridge發布數據 //?eventName:?用于標識事件名 //?data:?為傳遞的數據 JSBridge.publish('PAGE_EVENT',?{?eventName:?'onTest',?data:?{}?}) WKWebView?初始化
WKWebView 初始化
WKUserContentController?*userContentController?=?[WKUserContentController?new]; ????NSString?*souce?=?@"window.__fcjs_environment='miniprogram'"; ????WKUserScript?*script?=?[[WKUserScript?alloc]?initWithSource:souce?injectionTime:WKUserScriptInjectionTimeAtDocumentStart?forMainFrameOnly:true]; ????[userContentController?addUserScript:script]; ????[userContentController?addScriptMessageHandler:self?name:@"publishHandler"]; ????WKWebViewConfiguration?*wkWebViewConfiguration?=?[WKWebViewConfiguration?new]; ????wkWebViewConfiguration.allowsInlineMediaPlayback?=?YES; ????wkWebViewConfiguration.userContentController?=?userContentController; ????if?(@available(iOS?9.0,?*))?{ ????????[wkWebViewConfiguration.preferences?setValue:@(true)?forKey:@"allowFileAccessFromFileURLs"]; ????} ????WKPreferences?*preferences?=?[WKPreferences?new]; ????preferences.javaScriptCanOpenWindowsAutomatically?=?YES; ????wkWebViewConfiguration.preferences?=?preferences; ????self.webView?=?[[WKWebView?alloc]?initWithFrame:self.view.bounds?configuration:wkWebViewConfiguration]; ????self.webView.clipsToBounds?=?YES; ????self.webView.allowsBackForwardNavigationGestures?=?YES; ????[self.view?addSubview:self.webView]; ????NSString?*urlStr?=?[[NSBundle?mainBundle]?pathForResource:@"view.html"?ofType:nil]; ????NSURL?*fileURL?=?[NSURL?fileURLWithPath:urlStr]; ????[self.webView?loadFileURL:fileURL?allowingReadAccessToURL:fileURL];
WKWebView 事件回調處理
//?執行視圖層事件回調 -?(void)callSubscribeHandlerWithEvent:(NSString?*)eventName?param:(NSString?*)jsonParam { ????NSString?*js?=?[NSString?stringWithFormat:@"FinChatJSBridge.subscribeHandler('%@',%@)",eventName,jsonParam]; ????[self?evaluateJavaScript:js?completionHandler:nil]; } -?(void)evaluateJavaScript:(NSString?*)javaScriptString?completionHandler:(void(^)(id?result,NSError?*error))completionHandle { ????[self.webView?evaluateJavaScript:javaScriptString?completionHandler:completionHandler]; } #pragma?mark?-?WKScriptMessageHandle //?視圖層JSBridge請求接收處理 -?(void)userContentController:(WKUserContentController?*)userContentController?didReceiveScriptMessage:(WKScriptMessage?*)message { ????if?([message.name?isEqualToString:@"publishHandler"])?{ ????????NSString?*e?=?message.body[@"event"]; ????????[self.service?callSubscribeHandlerWithEvent:e?param:message.body[@"paramsString"]]; ????} }
視圖層代碼
function?onTest()?{ console.log('aaa') FinChatJSBridge.subscribe('PAGE_EVENT',?function(params)?{ document.getElementById('testId').innerHTML?=?params.data.title }) FinChatJSBridge.publish('PAGE_EVENT',?{ eventName:?'onTest', data:?{} }) }
邏輯層代碼
//?page?對像模擬 var?Page?=?{ ??setData:?function(data)?{ ????ServiceJSBridge.publish('PAGE_EVENT',?{ ??????eventName:?'onPageDataChange', ??????data:?data ????}) ??}, ??methods:?{ ????onTest:?function()?{ ??????Page.setData({ ????????title:?'我來自JS代碼更新' ??????}) ??????console.log('my?on?Test') ????} ??} } var?onWebviewEvent?=?function(fn)?{ ??ServiceJSBridge.subscribe('PAGE_EVENT',?function(params)?{ ????var?data?=?params.data, ??????eventName?=?params.eventName ????fn({ ??????data:?data, ??????eventName:?eventName ????}) ??}) } var?doWebviewEvent?=?function(pEvent,?params)?{ ??//?do?dom?ready ??if?(Page.methods.hasOwnProperty(pEvent))?{ ????Page.methods[pEvent].call(params) ??} } onWebviewEvent(function(params)?{ ??var?eventName?=?params.eventName ??var?data?=?params.data ??return?doWebviewEvent(eventName,?data) })
文檔中心: Document
本文示例代碼: https://github.com/finogeeks/fino-applet
相關文章:凡泰極客私有云小程序技術
關于凡泰極客:幫助金融機構乃至任何希望擁有類似技術的其他行業機構,建立“碎片”的集散地、降低管理成本、提高研發效能,形成自己的數字化生態、與客戶和伙伴建立真正的數字化連接。
小程序
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。