6.2.3 自定义组件
自定义组件是开发者可以自行扩充的组件。开发者可以将常用的节点树结构提取成自定义组件,实现代码复用。
1. ShadowTree的概念
我们以下面的代码为例来阐述Shadow Tree的概念。
代码清单6-2 页面节点树(Composed Tree)
<view>
<input-with-label>
<label>
TEXT
</label>
<input />
</input-with-label>
</view>
这里如果将input-with-label抽象成一个组件,那么可以将整个节点树拆分成两部分。
代码清单6-3 组件节点树(Shadow Tree)
<label><slot/></label>
<input />
代码清单6-4 调用组件的节点树
<view>
<input-with-label>
TEXT
</input-with-label>
</view>
在Exparser的组件模型中,这两个节点树可以被拼接成上方的页面节点树。其中,组件的节点树称为“ShadowTree”,即组件内部的实现;最终拼接成的页面节点树被称为“Composed Tree”,即将页面所有组件节点树合成之后的树。在进行了这样的组件分离之后,整个页面节点树实质上被拆分成了若干个ShadowTree(页面的body实质上也是一个组件,因而也是一个ShadowTree)。
同时,各个组件也将具有各自独立的逻辑空间。每个组件都分别拥有自己的独立的数据、setData调用,createSelectorQuery也将运行在Shadow Tree的层面上。关于具体如何使用自定义组件特性,这里不再详细讨论,请参阅小程序开发文档。
2. 运行原理
在使用自定义组件的小程序页面中,Exparser将接管所有的自定义组件注册与实例化。从外部接口上看,小程序基础库提供有Page和Component两个构造器。以Component为例,在小程序启动时,构造器会将开发者设置的properties、data、methods等定义段,写入Exparser的组件注册表中。这个组件在被其它组件引用时,就可以根据这些注册信息来创建自定义组件的实例。Page构造器的大体运行流程与之相仿,只是参数形式不一样。这样每个页面就有一个与之对应的组件,称为“页面根组件”。
在初始化页面时,Exparser会创建出页面根组件的一个实例,用到的其他组件也会响应创建组件实例(这是一个递归的过程)。组件创建的过程大致有以下几个要点:
- 根据组件注册信息,从组件原型上创建出组件节点的JS对象,即组件的this;
- 将组件注册信息中的data 复制一份,作为组件数据,即this.data;
- 将这份数据结合组件WXML,据此创建出Shadow Tree,由于Shadow Tree中可能引用有其他组件,因而这会递归触发其他组件创建过程;
- 将ShadowTree拼接到Composed Tree上,并生成一些缓存数据用于优化组件更新性能;
- 触发组件的created生命周期函数;
- 如果不是页面根组件,需要根据组件节点上的属性定义,来设置组件的属性值;
- 当组件实例被展示在页面上时,触发组件的attached 生命周期函数,如果Shadw Tree中有其他组件,也逐个触发它们的生命周期函数。
3. 组件间通信
不同组件实例间的通信有WXML属性值传递、事件系统、selectComponent和relations等方式。其中,WXML属性值传递是从父组件向子组件的基本通信方式,而事件系统是从子组件向父组件的基本通信方式。
Exparser的事件系统完全模仿Shadow DOM的事件系统。在通常的理解中,事件可以分为冒泡事件和非冒泡事件,但在ShadowDOM体系中,冒泡事件还可以划分为在Shadow Tree上冒泡的事件和在Composed Tree上冒泡的事件。如果在Shadow Tree上冒泡,则冒泡只会经过这个组件Shadow Tree上的节点,这样可以有效控制事件冒泡经过的范围。
代码清单6-5 input-with-label的WXML
<label>
<input />
<slot />
</label>
代码清单6-6 页面WXML
<view>
<input-with-label>
<button />
</input-with-label>
</view>
用上面的例子来说,当在 button 上触发一个事件时:
l 如果事件是非冒泡的,那只能在 button 上监听到事件;
l 如果事件是在 Shadow Tree 上冒泡的,那 button 、 input-with-label 、view 可以依次监听到事件;
l 如果事件是在 Composed Tree 上冒泡的,那 button 、 slot 、label 、 input-with-label 、 view 可以依次监听到事件。
在自定义组件中使用triggerEvent触发事件时,可以指定事件的bubbles、composed和capturePhase属性,用于标注事件的冒泡性质。
代码清单6-7 triggerEvent事例
Component({
methods: {
helloEvent: function() {
this.triggerEvent('hello', {}, {
bubbles: true, // 这是一个冒泡事件
composed: true, // 这个事件在Composed Tree 上冒泡
capturePhase: false // 这个事件没有捕获阶段
})
}
}
})
小程序基础库自身也会通过这套事件系统提供一些用户事件,如tap、touchstart和form组件的submit等。其中,tap等用户触摸引发的事件是在ComposedTree上的冒泡事件,其他事件大多是非冒泡事件。