简介
- JavaScript 的组件化界面框架
- 对旧版小程序组件框架的重写,保持对旧版小程序组件框架特性的兼容
- 不依赖于小程序环境,可以独立运行在 web 或其他 JavaScript 环境下
- 单线程版本,更好的适配 skyline 引擎,拥有更好的性能。
旧版框架对比
- 各类新增特性
- 性能更好
- list-diff
小程序适配指引
-
{ "componentFramework": "glass-easel" }
-
语法变更点适配 (webview 暂时不兼容,会以 exparser 兼容模式运行)
代码实例
-
小程序语法(需借助插件编译,方可运行)
-
web 语法
javascript// 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 不会主动触发页面生命周期,而是类似发布订阅模式,由某个组件主动触发页面生命周期,然后会递归调用所有子组件的页面生命周期监听时间,算是框架提供的一种通信机制。
js
// 使用 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 -> 双向链表的映射关系也会被存储为平铺的结构
js
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
-
代码示例
jsexport 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
- 自己编写库时也可以参考这种方式,提供上层操作,生成统一结构,由调用方传入自定义实例