专栏首页凡泰极客科技Blog深入小程序系列之一:小程序核心原理及模拟
原创

深入小程序系列之一:小程序核心原理及模拟

本文将介绍小程序的核心视图层逻辑层分离架构,并通过 iOS 的代码来模拟这种双线程模型。

什么是小程序

小程序是一种新的移动应用程序格式,是一种依赖 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 初始化

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: {}
  })
}

<div id="testId">我来自视图层!</div>
<input type="button" value="调用JS逻辑层setData" style="border-radius:15px;background:#ed0c50;border: #EDD70C;color: white;font-size: 14px; width: 80%;" onclick="onTest();" />

逻辑层代码

// 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

相关文章:凡泰极客私有云小程序技术

关于凡泰极客:帮助金融机构乃至任何希望拥有类似技术的其他行业机构,建立“碎片”的集散地、降低管理成本、提高研发效能,形成自己的数字化生态、与客户和伙伴建立真正的数字化连接。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入小程序系列(三) ReactNative和小程序混编

    本文我们将开一下脑洞,在 ReactNative 工程基础上下集成及运行小程序方案。

    凡泰极客
  • 开源商业模式促进金融业科技生态的发展

    在《从高盛的技术“开源”看金融业软件发展未来》中,总结了一些传统金融机构IT研发所遇到的放不开手脚的问题,并提到金融行业是时候与时俱进的借鉴采纳开源科技公司的做...

    凡泰极客
  • 从高盛的技术“开源”看金融业软件发展未来

    去年11月份CNBC的一则新闻,报道了华尔街巨头高盛要把自己投入了14年研发的一个技术平台Alloy以及专门为这个平台所设计的语言,免费、开源共享给华尔街的其他...

    凡泰极客
  • 1 小程序开发 - 获取用户登录信息

    用户5927264
  • 如何设计一个良好的流系统?(下)

    在Streaming 101中,作者引入了窗口和时间的概念,在本文中,作者为了解决流处理系统无法精确的处理结果的问题,提出了下面三个概念:

    哒呵呵
  • 独家 | 用pandas-profiling做出更好的探索性数据分析(附代码)

    探索性数据分析已失势,Pandas-profiling万岁!用更省力的办法完美呈现你的数据。

    数据派THU
  • 【目标检测】Uber | CVPR2019 | 自动驾驶3D目标检测任务中,LaserNet运行时间少,大数据集上性能SOTA

    本文提出了一种用于自动驾驶激光雷达3D目标检测计算效率高的方法——LaserNet。效率提高是由于在传感器的原生范围视图中处理LiDAR数据,其中输入数据自然紧...

    CNNer
  • bootstrap 弹出框 显示详细内容 常用样式

    <div class="container" style="padding: 100px 50px 10px;" > <button type="but...

    用户5760343
  • 翻译 | 玩转 React 表单 —— 受控组件详解

    原文地址:React.js Forms: Controlled Components 原文作者:Loren Stewart 译者:小 B0Y 校对者:珂珂君 本...

    iKcamp
  • 谷歌大脑新算法,不折腾TPU就能加快AI训练速度

    GPU和像谷歌TPU这样的硬件加速器大大加快了神经网络的训练速度,推助AI迅速成长,在各个领域发挥超能力。

    量子位

扫码关注云+社区

领取腾讯云代金券