likes
comments
collection
share

glass-easel

作者站长头像
站长
· 阅读数 8

简介

  • JavaScript 的组件化界面框架
  • 对旧版小程序组件框架的重写,保持对旧版小程序组件框架特性的兼容
  • 不依赖于小程序环境,可以独立运行在 web 或其他 JavaScript 环境下
  • 单线程版本,更好的适配 skyline 引擎,拥有更好的性能。

旧版框架对比

  • 各类新增特性
  • 性能更好
    • list-diff glass-easel

小程序适配指引

适配指引

  • { "componentFramework": "glass-easel" }

  • 语法变更点适配 (webview 暂时不兼容,会以 exparser 兼容模式运行)

代码实例

  • 小程序语法(需借助插件编译,方可运行)

  • web 语法

    // hello-world.js
    import * as glassEasel from 'glass-easel'
    
    // 定义一个组件空间
    const componentSpace = new glassEasel.ComponentSpace()
    
    // 组件模板
    const template = `
      <div class="blue">
        <span>{{ hello }}</span>
      </div>
    `
    
    // 定义组件
    export const helloWorld = componentSpace.defineComponent({
      // 组件所使用的模板
      template: compileTemplate(template),
      // 用在组件模板上的数据
      data: {
        hello: 'Hello world!'
      },
      lifetimes: {
        attached() {
          // 事件携带的数据
          const detail = { hello: 'data' }
          // 触发事件
          this.triggerEvent('customEvent', detail)
        },
      },
    })
    
    import * as glassEasel from 'glass-easel'
    import { helloWorld } from './hello-world'
    
    // 创建根组件实例
    const domBackend = new glassEasel.domlikeBackend.CurrentWindowBackendContext()
    const rootComponent = glassEasel.Component.createWithContext('body', helloWorld, domBackend)
    
    // 将组件插入到 DOM 树中
    const placeholder = document.createElement('span')
    document.body.appendChild(placeholder)
    glassEasel.Element.replaceDocumentElement(rootComponent, document.body, placeholder)
    
  • 借助官方提供的编译器进行同构开发 编译器

新增特性

页面生命周期

  • glass-easel 不会主动触发页面生命周期,而是类似发布订阅模式,由某个组件主动触发页面生命周期,然后会递归调用所有子组件的页面生命周期监听时间,算是框架提供的一种通信机制。
// 使用 Definition API 添加页面生命周期回调函数
export const myComponent = componentSpace.defineComponent({
  pageLifetimes: {
    someLifetime() { /* ... */ },
  },
})
// 或在 init 中添加页面生命周期回调函数
export const myComponent = componentSpace.define()
  .init(function ({ pageLifetime }) {
    pageLifetime('someLifetime', function () { /* ... */ })
  })
  .registerComponent()

trait behavior

  • 现有普通的 behavior 允许将几个组件共享的部分抽离出来,精简代码实现。但有些时候,一些组件拥有类似的行为,但是具体的行为实现又不一样。基于此,glass-easel 提供了特征行为这一特性,类似于ts中的接口,由组件实现特征行为,外部调用仅关注组件的特征行为,不用关注组件本身。

树结构 - shadowtree

  • glass-easel 自身以 Shadow Tree 为单位来存储节点树。每个组件实例对应于一个 Shadow Tree 。

  • Shadow tree -> Composed tree(去掉shadowroot以及slot,就是真实的dom节点树)

  • Shadow Tree 是一个组件实例自身模板中包含的节点,包括 slot 节点本身,但不包括组件的使用者放入 slot 中的内容;

  • 每个 Shadow Tree 都以一种特殊的 ShadowRoot 节点为根节点。

  • 提供了遍历节点的方法,dom 操作方法等挂载在 shadow root 上

动态slot

  • glass-easel 框架中,对于 slot name 重复的情况,仅会使用第一个出现的 slot,后续的 slot 会以 name -> 双向链表映射的方式存储在 shadowroot 实例中,供后续使用
  • 基于以上,glass-easel 提供了动态 slot 的能力,在显式声明动态 slot 时,所有同名的 slot 均会生效。
  • 实现方面,glass-easel 在构建节点树的时候,会同步构建一条双向链表,这条双向链表存储了所有的 slot 信息,并且每一 shadowroot 实例上都存储属于自己节点实例的slot信息,在链表中的起始以及终止节点。动态 slot 模式下,原本 name -> 双向链表的映射关系也会被存储为平铺的结构
export const childComponent = componentSpace.defineComponent({
  options: {
    dynamicSlots: true,
  },
  template: compileTemplate(`
    <block wx:for="{{ list }}">
      <slot />
    </block>
  `),
  data: {
    list: ['A', 'B', 'C'],
  },
})
export const myComponent = componentSpace.defineComponent({
  using: {
    child: childComponent,
  },
  template: compileTemplate(`
    <div>
      <child>
        <div class="a" />
      </child>
    </div>
  `),
})

新增MutationObserver

  • 代码示例

    export const myComponent = componentSpace.defineComponent({
      lifetimes: {
        attached() {
          // 监听 Shadow Tree 上的所有属性变化
          glassEasel.MutationObserver.create((ev) => {
            // ...
          }).observe(this.shadowRoot, { subtree: true, properties: true })
        }
      },
    })
    
  • 调用 mutataion observer 时,记录下对应的 listener,在创建 shadow root 时会将 listener 存储下来,后续 setdata 通过绑定映射表或者列表diff的方式通知所有的依赖更新

数据更新

  • 绑定映射表更新

    • 编译 wxml 模板是就可以知道哪一部分可以被更新,通过调用 setdata 来显式更新对应的key的 oberserver 等,过程中会生成一份 change 数据的深拷贝,然后触发模板引擎更新(根据改变的数据字段名,progen 格式(自定义的结构,由 wxml 经过模版编译得到)更新)
  • 列表 diff

    • 列表渲染无法使用绑定映射表更新,因为需要最小改变(例如某一项移动等),需要对两个给定的 key 数组,归纳出一组合适的移动、创建、删除操作,并使得操作总数尽可能少。
    • 首先进行预处理,使 key 值唯一(重复的 key 会被添加后缀唯一化),
      • 如果没有指定key,进行快速比较(末尾添加,或者仅更新非key字段也可以快速比较),默认此时是不会发生项移动的,只可能在列表的末尾发生追加或删除
    • 最长递增子序列算法(二分法O(n log n))
      • 删除和创建的操作是固定的,只需要找出不变的项所需要最小的移动
      • 找到最长的子序列(key)就是最小的改动
      • 统计出删除或者移动操作

自定义后端,自定义模板引擎

  • 生成统一的上层结构(composed tree -> shadow root)节点树,由glass-easel转换为对应的节点操作(document. eg)
  • 自定的后端需要根据官方提供的协议编写对应的代码
    • Composed Mode
    • shadow Mode
    • Dom-like mode
  • 自己编写库时也可以参考这种方式,提供上层操作,生成统一结构,由调用方传入自定义实例

参考文档