Heim  >  Artikel  >  WeChat-Applet  >  Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung

Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung

coldplay.xixi
coldplay.xixinach vorne
2020-11-04 17:45:373079Durchsuche

In der Kolumne „Tutorial zur WeChat Mini-Programmentwicklung“ wird die Laufzeitlösung für das Rax-Miniprogramm vorgestellt.

Im März 2020 veröffentlichte das Rax-Applet nach der Unterstützung des Kompilierungszeitschemas eine Version, die das Laufzeitschema unterstützt. Bisher ist Rax immer noch das einzige kleine Programmentwicklungs-Framework in der Branche, das sowohl Lösungen zur Kompilierungszeit als auch zur Laufzeit unterstützt. In diesem Artikel werden Ihnen die Prinzipien der Rax-Applet-Laufzeitlösung und unsere Denkweise vorgestellt.

Überprüfung von Lösungen zur Kompilierungszeit

Entschlüsselung und Nachdenken über die Rax-Applet-LaufzeitlösungBevor wir Laufzeitlösungen vorstellen, werfen wir einen Blick darauf, was Lösungen zur Kompilierungszeit sind. Wie der Name schon sagt, konzentriert sich die Kompilierzeitlösung auf die Kompilierung, und das repräsentative Framework ist Taro v2.x. Es konvertiert JSX durch statische Kompilierung in die Vorlagensprache des Miniprogramms (d. h. WXML/AXML usw.) und ergänzt es dann mit leichtgewichtigem Laufzeit-JS-Code, um den Unterschied zwischen dem Lebenszyklus des Miniprogramms und dem React-Lebenszyklus auszugleichen , sodass Benutzer mit dem bekannten React DSL kleine Programme entwickeln können. Das Prinzip des Kompilierzeitschemas von Rax ähnelt dem von Taro v2.x. Einzelheiten zur Implementierung finden Sie in den vorherigen Artikeln Analyse des Prinzips des Rax-zu-Mini-Programm-Links (1) und Analyse des Prinzips des Rax Mini-Programm-Zusammenstellungsschema. Im Gegensatz zur Kompilierzeitlösung konzentriert sich die Laufzeitlösung auf die Implementierung von Rendering-Funktionen zur Laufzeit und ist nicht auf statische Kompilierung angewiesen. Daher gibt es nahezu keine grammatikalischen Einschränkungen, was auch ihr größtes Merkmal ist. Werfen wir einen Blick auf die Prinzipien der Laufzeitlösungsimplementierung.

Basic of Birth

Die zugrunde liegende Implementierung des Miniprogramms basiert eigentlich auf Web-Technologie, unterscheidet sich jedoch auf Entwicklerebene stark vom Web. Im Miniprogramm sind die Logikschicht und die Ansichtsschicht isoliert. Die Logikschicht leitet Daten an die Ansichtsschicht weiter, um das Rendern über die eindeutige Methode setData auszulösen, und die Ansichtsschicht löst den Code der Logikschicht aus Die Architektur ist wie folgt: Wie in der Abbildung gezeigt. Im Vergleich zur Webentwicklung können Entwickler mit JS die vom Browser bereitgestellte DOM/BOM-API aufrufen, um Inhalte nach Belieben zu bearbeiten und darzustellen. Die Architektur von Miniprogrammen ist geschlossener und sicherer, bedeutet aber auch, dass Webcode nicht direkt ausgeführt werden kann auf Mini-Programmen.

Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung

Bei modernen Front-End-Frameworks (React/Vue) erstellt die unterste Ebene grundsätzlich Ansichten durch Aufruf der DOM-API. Die Ansichtsebenenvorlage des Miniprogramms muss vom Entwickler im Voraus geschrieben werden, was bedeutet, dass die Methode der dynamischen Erstellung von DOM im Miniprogramm nicht zulässig ist. Die „Selbstreferenz“-Funktion der benutzerdefinierten Komponente des Miniprogramms eröffnet jedoch einen Durchbruch für die dynamische Erstellung von DOM. Die sogenannte Selbstreferenz bedeutet, dass die benutzerdefinierte Komponente die Verwendung selbst als untergeordneter Knoten unterstützt, was bedeutet, dass wir durch rekursive Referenz jede Ebene und Anzahl von DOM-Bäumen erstellen können.

Angenommen, die WXML-Vorlage eines benutzerdefinierten Applet-Komponentenelements lautet wie folgt: setData 方法将数据传递至视图层触发渲染,视图层则通过事件的方式触发逻辑层代码,其架构如下图所示。相比 Web 开发时开发者可以通过 JS 调用浏览器提供的 DOM/BOM API 随心所欲操作渲染内容,小程序的架构更加封闭也更安全,但也意味着 Web 代码无法直接在小程序上运行。

Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung

对于现代的前端框架(React/Vue)来说,底层基本都是通过调用 DOM API 来创建视图。而小程序的视图层模板是需要开发者事先写好的,这意味着动态创建 DOM 的方式在小程序中不被允许。但是,小程序的自定义组件具有的『自引用』特性为动态创建 DOM 打开了突破口。所谓自引用,就是自定义组件支持使用自己作为子节点,也就意味着通过递归引用的方式,我们能够构造任意层级和数量的 DOM 树。

举例来说,假设一个小程序自定义组件 element 的 WXML 模板如下所示:

<view>  <block>      <element></element>  </block></view><text>  {{r.content}}</text>复制代码

注意到,element 在模板中递归引用了自身,并通过条件判断终止递归。那么,当逻辑层通过 setData 传递了以下一份数据过来时:

{
  "nodeId": "1",
  "tagName": "view",
  "children": [
    {
      "nodeId": "2",
      "tagName": "text",
      “content”: “我是?"
    },
    {
      "nodeId": "3",
      “tagName": "text",
      "content": "rax"    }
  ]
}复制代码

最终呈现出来的视图便成了:

<view>
  <text>我是</text>
  <text>rax</text></view>复制代码

通过这种方式,我们巧妙地实现了在 WXML 模板固定的情况下,根据传入的 setData 数据来动态渲染视图的能力。而这,也正是运行时方案能够诞生的基础。

基本原理

Rax 的运行时方案脱胎自 kbone——微信官方推出的小程序与 web 端同构解决方案。kbone 的设计原理可以参考其官网介绍,简单总结就是通过在逻辑层模拟 DOM/BOM API,将这些创建视图的方法转换为维护一棵 VDOM 树,再将其转换成对应 setData

{   "root.children.[0].children.[1].class": "active"}复制代码
Beachten Sie, dass das Element in der Vorlage rekursiv auf sich selbst verweist und die Rekursion durch bedingte Beurteilung beendet. Wenn die Logikschicht dann die folgenden Daten über setData weiterleitet: 🎜rrreee🎜Die endgültige Ansicht wird: 🎜rrreee🎜Auf diese Weise implementieren wir geschickt die WXML-Vorlage Rendern Sie die Ansicht basierend auf den eingehenden setData-Daten. Und das ist die Grundlage für die Geburt von Laufzeitlösungen. 🎜

Grundprinzip🎜🎜Die Laufzeitlösung von Rax ist von kbone abgeleitet, der isomorphen Lösung für Miniprogramme und das von WeChat offiziell eingeführte Web. Die Entwurfsprinzipien von kbone können auf der offiziellen Website nachgelesen werden. Eine einfache Zusammenfassung besteht darin, die DOM/BOM-API auf der Logikebene zu simulieren, diese Methoden zum Erstellen von Ansichten in die Pflege eines VDOM-Baums umzuwandeln und ihn dann in den entsprechenden setData Daten und rendern Sie schließlich die tatsächliche Ansicht rekursiv über die voreingestellte Vorlage. Das Grundprinzip des Prozesses von der DOM-API bis zur Pflege des VDOM-Baums ist nicht kompliziert und entspricht den grundlegenden Datenstrukturoperationen. 🎜

Studenten, die mit Rax vertraut sind, sollten wissen, dass Rax zur Unterstützung von Cross-Terminal über ein Treiberdesign verfügt. Tatsächlich können wir einen weiteren Treiber für das kleine Programm schreiben und dessen Schnittstellen-API basierend auf den oben genannten Prinzipien implementieren. Unsere endgültige Entscheidung bestand jedoch darin, den gesamten Rendering-Mechanismus über eine simulierte BOM/DOM-API auf niedrigerer Ebene abzuschließen. Die Überlegungen hierfür basieren zunächst auf der kbone-Entwicklung, die die schnellste Lösung darstellt. Der Treiber auf der Applet-Seite muss schließlich nur das Treiber-Dom auf der Web-Seite wiederverwenden. code>- und window-Variablen wurden simuliert; zweitens möchten wir Entwicklern ein Entwicklungserlebnis bieten, das näher am Web ist. Diese Lösung bedeutet, dass Entwickler zusätzlich zur Verwendung von JSX auch direkt die BOM/DOM-API zum Erstellen von Ansichten verwenden können, was flexibler ist. Wir betrachten alle auf dem Markt erhältlichen Mini-Programm-Laufzeit-Frameworks. Remax stellt über React-Reconciler eine direkte Schnittstelle zu Mini-Programmen der VDOM-Ebene her (ähnlich dem oben erwähnten Rax-Mini-Programm-Treiberdesign), während sich sowohl kbone als auch Taro 3.0 für die Verwendung von Simulationen entscheiden . Webumgebung zur Implementierung des Renderings. Dies hängt auch mit der Designabsicht des Framework-Entwicklers zusammen. Die Meinungen gehen auseinander. Das grundlegende schematische Diagramm der Rax-Applet-Laufzeitlösung lautet wie folgt: Grundlegendes schematisches Diagramm der Rax-Applet-Laufzeitdocumentwindow 变量都已经模拟好;第二,则是因为我们想为开发者提供更贴近 web 的开发体验。这套方案意味着开发者除了使用 JSX 之外,也是支持直接使用 BOM/DOM API 创建视图的,灵活度会更高一点。我们把目光拉长到整个市面上的小程序运行时框架,remax 通过 react-reconciler 直接从 VDOM 层和小程序对接(类似上面说的 Rax 小程序 driver 设计),而 kbone 和 Taro 3.0 都选择通过模拟 Web 环境来实现渲染。这也与框架开发人员的设计意图有关,见仁见智。Rax 小程序运行时方案的基本原理图如下所示:Rax 小程序运行时基本原理图

事件系统

Rax 小程序运行时中,模拟 DOM/BOM API 的库为 Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung,其支持的 API 如下:Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung

除了处理渲染数据外,另一个比较重要的事情便是事件系统。其通过 EventTarget 基类实现了一套完整的事件派发机制。逻辑层 DOM 节点均继承自 EventTarget,通过唯一的 nodeId 来收集自身绑定事件。视图层模板上的每个内置组件都会绑定 nodeId,并监听所有可触发的事件,比如一个简单的 view 标签,会将 bindtap/bindtouchstart/bindtouchend 等事件都进行绑定。在事件触发时,通过 event.currentTarget.dataset.nodeId 获取到目标节点 id,再触发该节点上用户绑定的对应函数。

工程设计

Rax 小程序运行时的工程主体流程 follow 了 Rax Web 的设计,Web 端 Webpack 打包出的 JS Bundle 可以在小程序运行时中复用。我们通过插件将 Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung 模拟出的 window 和 document 变量注入该 bundle,再生成一个固定的小程序项目骨架,在 app.js 中加载 JS Bundle 即可。其整体工程结构如下图所示:Rax 小程序运行时工程结构

MPA or SPA ?

以上架构是逐步演进的结果。最初,我们使用了 webpack 的多 entry 模式打包运行时小程序代码,也就是每个页面都会作为一个 entry 独立打包。这使得从行为上来说小程序更像一个 MPA。这带来的问题就是页面间公共依赖的代码不在同一内存中执行,与原生小程序表现不符。这个差异导致我们最终决定变更工程打包模式。目前版本的 Rax 运行时小程序更符合 SPA 的形式,所有的业务代码都打包到了一个 JS 文件中。

我们将 Rax 工程入口的 rax-app 包在小程序运行时上的链路做了一定改造,其在初始化时会根据路由返回各个页面的 render 函数,该 render 函数创建 root 节点(document.createElement)将对应的 Rax 组件挂载至 其上,并将 root 节点 append 到 body 节点(document.body.appendChild)。小程序每个页面在 onLoad 生命周期中,会创建一个独立的 document 并设置为全局变量,然后调用其对应的 render

Ereignissystem

Rax Small Wenn das Programm ausgeführt wird, ist die Bibliothek, die die DOM/BOM-API simuliert, Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung. Die unterstützten APIs lauten wie folgt: Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung🎜🎜Neben der Verarbeitung von Rendering-Daten noch eine weitere wichtige Sache ist das Ereignissystem. Es implementiert einen vollständigen Event-Dispatch-Mechanismus über die Basisklasse EventTarget. Alle DOM-Knoten der logischen Ebene erben von EventTarget und sammeln ihre eigenen Bindungsereignisse über die eindeutige nodeId. Jede integrierte Komponente in der Ansichtsebenenvorlage wird an nodeId gebunden und überwacht alle auslösbaren Ereignisse. Beispielsweise bindet ein einfaches Ansichts-Tag bindtap/bindtouchstart/bindtouchend und andere Ereignisse. Wenn das Ereignis ausgelöst wird, wird die Zielknoten-ID über event.currentTarget.dataset.nodeId abgerufen und dann die entsprechende benutzergebundene Funktion auf dem Knoten ausgelöst. 🎜

Konstruktionsdesign

🎜Der Hauptprozess des Projekts, wenn das Rax-Miniprogramm ausgeführt wird, folgt dem Design von Rax Web, das von Webpack auf der Webseite gepackt wird kann bei laufendem Miniprogramm wiederverwendet werden. Wir fügen die von Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung simulierten Fenster- und Dokumentvariablen über das Plug-in in das Bundle ein, generieren ein festes Miniprogramm-Projektskelett und laden das JS-Bundle in app.js. Die Gesamtprojektstruktur ist in der folgenden Abbildung dargestellt: Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung🎜

MPA oder SPA?

🎜Die obige Architektur entwickelt sich schrittweise weiter Ergebnis. Zunächst verwendeten wir den Multi-Entry-Modus von Webpack, um den Laufzeit-Applet-Code zu packen, d. h. jede Seite wurde unabhängig als Eintrag gepackt. Dadurch verhält sich das Applet eher wie ein MPA. Das Problem besteht darin, dass der Code, der häufig von Seiten abhängt, nicht im selben Speicher ausgeführt wird, was nicht mit der Leistung des nativen Applets übereinstimmt. Dieser Unterschied führte zu unserer endgültigen Entscheidung, das Projektverpackungsmodell zu ändern. Die aktuelle Version des Rax-Laufzeit-Applets entspricht eher der SPA-Form und alle Geschäftscodes sind in eine JS-Datei gepackt. 🎜🎜Wir haben bestimmte Änderungen am Link des Rax-App-Pakets des Rax-Projekteingangs in der Mini-Programmlaufzeit vorgenommen. Während der Initialisierung wird die render-Funktion jeder Seite entsprechend der Route zurückgegeben . Die Funktion render erstellt den Stammknoten (document.createElement), hängt die entsprechende Rax-Komponente daran an und hängt den Stammknoten an den Hauptknoten an (). document.body.appendChild ). Während des onLoad-Lebenszyklus jeder Seite des Miniprogramms wird ein unabhängiges Dokument erstellt und als globale Variable festgelegt, und dann wird die entsprechende render-Funktion aufgerufen Rendern Sie jede Seite unabhängig. 🎜

性能调优

从上面的小程序运行时原理来看,其性能相比原生是存在一定差距的,这主要由以下几个方面造成:第一:逻辑层运行完整的 Rax + 通过模拟 DOM/BOM API 处理 VDOM 并生成 setData 数据,需要消耗更多的计算时间;第二,相比原生小程序需要传递更多 setData 数据,如果容器层数据序列化能力较弱,会大大增加数据传输耗时;第三,视图层通过自定义组件递归动态生成视图,而我们知道递归动作本身就是一个性能损耗点。此外,由于无法预先知晓用户需要绑定的属性和事件,自定义组件模板中只能将所有属性和事件预先绑好,这导致小程序运行过程中会触发很多无用的事件,进一步加重负担。经过我们的 benchmark 计算,在支付宝小程序平台上,运行时小程序框架(包括 Rax/Taro/Remax 等)与原生小程序存在约 40% 的性能差距。

Rax 小程序运行时发布后,经测试其性能相比其他运行时框架存在着较为明显的差距,于是我们启动了性能调优的专项计划。通过以下方面的重构,成功将 Rax 小程序运行时小程序的性能拉升至业界领先水平,与 Taro/Remax 基本处于同一水平线。

  • 更新数据精确化。在旧版本中,setData 的数据是全量更新的,虽然有 dom 子树分割批量更新的设计,但是数据传输仍然存在大量冗余。重构版本中,Rax 增加了节点渲染判断,未挂载节点无须触发更新;将所有更新收拢至顶层 root 节点统一批量处理, 并且通过精确计算数据更新的 path,实现局部更新。比如某次更新节点的 class 属性时,setData 的数据可能是:

    {   "root.children.[0].children.[1].class": "active"}复制代码
  • 内置小程序组件无需维护其属性列表,而是根据用户传参直接赋值。旧版本中,我们维护了所有内置组件的属性,在获取属性值的时候均需要调用 domNode.getAttribute,具有一定性能开销。重构版本 Rax 直接根据用户传参给属性赋值,并将默认值设置的操作移至视图层 WXS/SJS 中处理。

  • 更新 Entschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung 中的数据结构。经过梳理,Rax 移除了冗余的 tree 数据,重写了 getaElementById 等 API;重构了 attribute、classList 等类;使用了更符合场景需要的 Map/Set 等数据结构,提升了整体的数据处理性能。

  • 渲染模板优化。在支付宝小程序中,Rax 使用 template 进行递归调用;在微信中,Rax 使用 template 调用 element 再调用 template 的形式以避免微信端递归调用 template 的层数限制。在模板中,我们尽量使用 template is 语法进行判断,减少 a:if/wx:if 条件判断,提升模板递归时的性能。

混合使用

无论是出于旧有业务的迁移,或者是出于性能考虑,Rax 小程序运行时中都存在着混合使用的需求。目前,Rax 已经打通与小程序内置组件、小程序自定义组件、小程序页面、小程序插件混合使用的能力。这其中,使用小程序自定义组件是最为复杂的。

与小程序自定义组件混用

在 Rax 中使用小程序自定义组件,其引入路径需要与 usingComponents 保持一致(例如 import CustomComp from '../components/CustomComp/index')。 在编译阶段,Rax 工程使用 Babel 插件进行代码扫描,检测到 JSX 中使用的某个组件是小程序自定义组件(根据其引入路径是否存在同名 axml 文件)时,会将其使用到的属性和事件进行缓存,然后通过 webpack 插件动态生成至递归模板中。在运行时中的创建节点阶段,通过查询缓存判断节点是否为自定义组件。若是自定义组件,则其渲染数据中会插入缓存中的属性,并且绑定事件至该自定义组件实例。

与编译时组件混用(双引擎混合)

通过 Rax 小程序编译时方案产出的组件,从使用形态上来说,可以直接视为小程序自定义组件。而 Rax 工程加强了运行时与编译时的联系,当在 Rax 小程序运行时中使用编译时组件 npm 包时,用户无需引入组件的具体路径,只需像使用普通组件时一样引入,Rax 工程将自动根据该组件 package.json 中是否存在 miniappConfig 字段来判断其是否为一个 Rax 多端组件,然后直接使用其编译时的组件实现。

Zukünftige Optimierungsrichtungen

Rax ist die einzige kleine Programmentwicklungslösung in der Branche, die sowohl Kompilierzeit- als auch Laufzeit-Engines unterstützt. Durch die Kombination von Dual-Engines kann ein perfektes Gleichgewicht zwischen Leistung und Entwicklungseffizienz erreicht werden. In Zukunft wird Rax eine flexiblere Dual-Engine-Mischnutzungsmethode implementieren, z. B. die Unterstützung der Bezeichnung einer Komponente, die mit der Kompilierzeit-Engine in einem einzigen Projekt kompiliert werden soll, was dem Unternehmen eine höhere Flexibilität bietet.

Zusammenfassung

Das Obige ist die Hauptanalyse der Rax-Applet-Laufzeitlösung. Die Laufzeitlösung löst die Syntaxeinschränkungen, die der Kompilierungszeitlösung innewohnen, es gibt jedoch auch offensichtliche Leistungseinschränkungen. Man kann sagen, dass es zum aktuellen Zeitpunkt des Jahres 2020 noch keine sogenannte Wunderwaffe für die Entwicklung von Miniprogrammen gibt. Vielleicht ist die Fusion der Dual-Engines des Rax-Miniprogramms eine optimale Lösung in einem relativ breiten Spektrum. Niemand kann sagen, wie weit Miniprogramme, die gegen die Standards verstoßen, auch in Zukunft noch mit verschiedenen Problemen konfrontiert sein werden. Aus der Perspektive eines kleinen Programmentwicklungsframeworks hoffe ich nur, dass alle Entwickler das für sie am besten geeignete Framework auswählen und die Entwicklung kleiner Programme schnell und effizient abschließen können.

Verwandte kostenlose Lernempfehlungen: WeChat Mini-Programmentwicklungs-Tutorial

Das obige ist der detaillierte Inhalt vonEntschlüsselung und Nachdenken über die Rax-Applet-Laufzeitlösung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.im. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen