行业现状

现在渲染到 native 有两个流派:

  • Flutter
    • 使用 Skia 高性能渲染引擎 GPU 直绘
    • 使用 Dart 语言开发
  • React Native、Weex、Taro、Hippy、Plato
    • 通过 Bridge 与 JSCore 传输指令绘制
    • 使用 Javascript 语言开发
    • JSCore 和 Native 各自维护同样的一棵 DOM 树

这里 Omi 使用第二种方式实现 。

因为一切 Web Components 的根基都是 。可以看到 Omi 的自定义元素是通过继承 WeElement:

而通过 Omi 源码可以发现,WeElement 是继承自 HTMLElement

  1. class WeElement extends HTMLElement {
  2. ...
  3. }

既然要在 JSCore 里向 Native 发送指令,那么首先要保证能正常运行。但是在 JSCore 里是没有 DOM 和 BOM, HTMLElement 属于 DOM, 自然也就没有。所以 Omi 的项目在 JSCore 里会报错。所以解决这个问题的答案也就浮出水面。

模拟 HTMLElement

  • HTMLElement 继承自父接口 Element 和 GlobalEventHandlers 的属性
  • Element 继承自 Node (常用的 appendChild、removeChild、insertBefore 都定义在 Node 中)

但是我们实现未必需要和浏览器的实现完全一致,更加不用实现所有的 API。所以 omi-native 仅仅实现了:

  • Element
  • HTMLElement
  • Document

其中 HTMLElement 继承自 Element,具体需要实现哪些API,这里优先梳理出 Omi 使用的 DOM API:

  • HTMLElement
    • connectedCallback
    • disconnectedCallback
  • Element
    • addEventListener
    • removeEventListener
    • removeAttribute
    • setAttribute
    • removeChild
    • appendChild
    • replaceChild
    • style
  • Document
    • createElement

所以只要实现包括上面这些 API 就能保证 Omi 项目能够在 JSCore 里跑起来不报错。但是仅仅不报错,是不够的,还需要来回发送指令。
指令传输的意义在于让 Native 维护的 DOM Tree 和 JSCore 维护的 DOM Tree 保持一致。而指令发送的频率会直接影响耗时,指令发送频率越低越好。所以在把 bridge 通讯注入到 appendChild、removeChild 等方法中时,遵循的原则是:

所以可想而知,document.createElement 或者悬空节点的 appendChildremoveChild 是不发送任何指令

Omi 自定义元素的生命周期如下所以:

Omi 的生命周期完全依赖 HTMLElementconnectedCallbackdisconnectedCallback

  • connectedCallback 元素被插入到页面时候触发
  • disconnectedCallback 元素从页面移除时触发

既然 HTMLElementElement 都是自己实现,所以可以控制 connectedCallbackdisconnectedCallback 的执行时机。因为你知道元素什么时候被插入到 DOM 树里。比如 append 的时候:

  1. if (!node.parentNode) {
  2. linkParent(node, this)
  3. insertIndex(node, this.childNodes, this.childNodes.length, true)
  4. if(this.connectedCallback){
  5. this.connectedCallback()
  6. }
  7. ...
  8. }

比如移除时:

事件绑定

由于 JS 里事件绑定的回调函数包含上下文信息,不能传输给客户端,所以只需要告诉 native 元素的 id 和事件绑定的类型,当客户端触发的时候只需传输回元素的 id 和事件的类型。

  1. addEventListener(type, handler) {
  2. if (!this.event[type]) {
  3. this.event[type] = handler
  4. this.ownerDocument.addEvent(this.ref, type)
  5. }

→ 戳这里看下源码