glass-easel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/glass_easel.all.d.ts +1 -0
- package/dist/glass_easel.all.js +2 -0
- package/dist/glass_easel.all.js.map +1 -0
- package/dist/glass_easel.domlike.global.d.ts +1 -0
- package/dist/glass_easel.domlike.global.js +2 -0
- package/dist/glass_easel.domlike.global.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/src/backend/backend_protocol.d.ts +119 -0
- package/dist/types/src/backend/backend_protocol.d.ts.map +1 -0
- package/dist/types/src/backend/composed_backend_protocol.d.ts +90 -0
- package/dist/types/src/backend/composed_backend_protocol.d.ts.map +1 -0
- package/dist/types/src/backend/domlike_backend_protocol.d.ts +76 -0
- package/dist/types/src/backend/domlike_backend_protocol.d.ts.map +1 -0
- package/dist/types/src/backend/mode.d.ts +46 -0
- package/dist/types/src/backend/mode.d.ts.map +1 -0
- package/dist/types/src/backend/suggested_backend_protocol.d.ts +30 -0
- package/dist/types/src/backend/suggested_backend_protocol.d.ts.map +1 -0
- package/dist/types/src/behavior.d.ts +428 -0
- package/dist/types/src/behavior.d.ts.map +1 -0
- package/dist/types/src/class_list.d.ts +79 -0
- package/dist/types/src/class_list.d.ts.map +1 -0
- package/dist/types/src/component.d.ts +291 -0
- package/dist/types/src/component.d.ts.map +1 -0
- package/dist/types/src/component_params.d.ts +239 -0
- package/dist/types/src/component_params.d.ts.map +1 -0
- package/dist/types/src/component_space.d.ts +164 -0
- package/dist/types/src/component_space.d.ts.map +1 -0
- package/dist/types/src/data_path.d.ts +5 -0
- package/dist/types/src/data_path.d.ts.map +1 -0
- package/dist/types/src/data_proxy.d.ts +107 -0
- package/dist/types/src/data_proxy.d.ts.map +1 -0
- package/dist/types/src/data_utils.d.ts +3 -0
- package/dist/types/src/data_utils.d.ts.map +1 -0
- package/dist/types/src/element.d.ts +275 -0
- package/dist/types/src/element.d.ts.map +1 -0
- package/dist/types/src/element_iterator.d.ts +43 -0
- package/dist/types/src/element_iterator.d.ts.map +1 -0
- package/dist/types/src/event.d.ts +104 -0
- package/dist/types/src/event.d.ts.map +1 -0
- package/dist/types/src/external_shadow_tree.d.ts +20 -0
- package/dist/types/src/external_shadow_tree.d.ts.map +1 -0
- package/dist/types/src/func_arr.d.ts +39 -0
- package/dist/types/src/func_arr.d.ts.map +1 -0
- package/dist/types/src/global_options.d.ts +111 -0
- package/dist/types/src/global_options.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +43 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/mutation_observer.d.ts +79 -0
- package/dist/types/src/mutation_observer.d.ts.map +1 -0
- package/dist/types/src/native_node.d.ts +8 -0
- package/dist/types/src/native_node.d.ts.map +1 -0
- package/dist/types/src/node.d.ts +49 -0
- package/dist/types/src/node.d.ts.map +1 -0
- package/dist/types/src/relation.d.ts +47 -0
- package/dist/types/src/relation.d.ts.map +1 -0
- package/dist/types/src/render.d.ts +3 -0
- package/dist/types/src/render.d.ts.map +1 -0
- package/dist/types/src/selector.d.ts +32 -0
- package/dist/types/src/selector.d.ts.map +1 -0
- package/dist/types/src/shadow_root.d.ts +136 -0
- package/dist/types/src/shadow_root.d.ts.map +1 -0
- package/dist/types/src/template_engine.d.ts +18 -0
- package/dist/types/src/template_engine.d.ts.map +1 -0
- package/dist/types/src/text_node.d.ts +32 -0
- package/dist/types/src/text_node.d.ts.map +1 -0
- package/dist/types/src/tmpl/index.d.ts +18 -0
- package/dist/types/src/tmpl/index.d.ts.map +1 -0
- package/dist/types/src/tmpl/native_rendering.d.ts +45 -0
- package/dist/types/src/tmpl/native_rendering.d.ts.map +1 -0
- package/dist/types/src/tmpl/proc_gen_wrapper.d.ts +80 -0
- package/dist/types/src/tmpl/proc_gen_wrapper.d.ts.map +1 -0
- package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts +50 -0
- package/dist/types/src/tmpl/proc_gen_wrapper_dom.d.ts.map +1 -0
- package/dist/types/src/tmpl/range_list_diff.d.ts +19 -0
- package/dist/types/src/tmpl/range_list_diff.d.ts.map +1 -0
- package/dist/types/src/trait_behaviors.d.ts +38 -0
- package/dist/types/src/trait_behaviors.d.ts.map +1 -0
- package/dist/types/src/virtual_node.d.ts +10 -0
- package/dist/types/src/virtual_node.d.ts.map +1 -0
- package/dist/types/tests/backend/domlike.test.d.ts +2 -0
- package/dist/types/tests/backend/domlike.test.d.ts.map +1 -0
- package/dist/types/tests/base/env.d.ts +29 -0
- package/dist/types/tests/base/env.d.ts.map +1 -0
- package/dist/types/tests/base/match.d.ts +9 -0
- package/dist/types/tests/base/match.d.ts.map +1 -0
- package/dist/types/tests/core/backend.test.d.ts +2 -0
- package/dist/types/tests/core/backend.test.d.ts.map +1 -0
- package/dist/types/tests/core/behavior.test.d.ts +2 -0
- package/dist/types/tests/core/behavior.test.d.ts.map +1 -0
- package/dist/types/tests/core/component_space.test.d.ts +2 -0
- package/dist/types/tests/core/component_space.test.d.ts.map +1 -0
- package/dist/types/tests/core/data_update.test.d.ts +2 -0
- package/dist/types/tests/core/data_update.test.d.ts.map +1 -0
- package/dist/types/tests/core/misc.test.d.ts +2 -0
- package/dist/types/tests/core/misc.test.d.ts.map +1 -0
- package/dist/types/tests/core/placeholder.test.d.ts +2 -0
- package/dist/types/tests/core/placeholder.test.d.ts.map +1 -0
- package/dist/types/tests/core/slot.test.d.ts +2 -0
- package/dist/types/tests/core/slot.test.d.ts.map +1 -0
- package/dist/types/tests/core/trait_behaviors.test.d.ts +2 -0
- package/dist/types/tests/core/trait_behaviors.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/binding_map.test.d.ts +2 -0
- package/dist/types/tests/tmpl/binding_map.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/event.test.d.ts +2 -0
- package/dist/types/tests/tmpl/event.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/expression.test.d.ts +2 -0
- package/dist/types/tests/tmpl/expression.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/lvalue.test.d.ts +2 -0
- package/dist/types/tests/tmpl/lvalue.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/native_rendering.test.d.ts +2 -0
- package/dist/types/tests/tmpl/native_rendering.test.d.ts.map +1 -0
- package/dist/types/tests/tmpl/structure.test.d.ts +2 -0
- package/dist/types/tests/tmpl/structure.test.d.ts.map +1 -0
- package/dist/types/tests/types/chaining.test.d.ts +2 -0
- package/dist/types/tests/types/chaining.test.d.ts.map +1 -0
- package/dist/types/tests/types/createElement.test.d.ts +2 -0
- package/dist/types/tests/types/createElement.test.d.ts.map +1 -0
- package/dist/types/tests/types/definition.test.d.ts +2 -0
- package/dist/types/tests/types/definition.test.d.ts.map +1 -0
- package/guide/zh_CN/advanced/binding_map_update.md +32 -0
- package/guide/zh_CN/advanced/build_args.md +28 -0
- package/guide/zh_CN/advanced/component_filter.md +70 -0
- package/guide/zh_CN/advanced/component_space.md +124 -0
- package/guide/zh_CN/advanced/custom_backend.md +53 -0
- package/guide/zh_CN/advanced/error_listener.md +32 -0
- package/guide/zh_CN/advanced/external_component.md +73 -0
- package/guide/zh_CN/advanced/template_engine.md +61 -0
- package/guide/zh_CN/appendix/backend_protocol.md +501 -0
- package/guide/zh_CN/appendix/list_diff_algorithm.md +406 -0
- package/guide/zh_CN/basic/beginning.md +94 -0
- package/guide/zh_CN/basic/component.md +156 -0
- package/guide/zh_CN/basic/event.md +169 -0
- package/guide/zh_CN/basic/lifetime.md +66 -0
- package/guide/zh_CN/basic/method.md +62 -0
- package/guide/zh_CN/basic/template.md +135 -0
- package/guide/zh_CN/data_management/advanced_update.md +170 -0
- package/guide/zh_CN/data_management/data_deep_copy.md +157 -0
- package/guide/zh_CN/data_management/data_observer.md +154 -0
- package/guide/zh_CN/data_management/property_early_init.md +31 -0
- package/guide/zh_CN/data_management/pure_data_pattern.md +21 -0
- package/guide/zh_CN/index.md +93 -0
- package/guide/zh_CN/interaction/behavior.md +52 -0
- package/guide/zh_CN/interaction/component_path.md +37 -0
- package/guide/zh_CN/interaction/generic.md +73 -0
- package/guide/zh_CN/interaction/placeholder.md +40 -0
- package/guide/zh_CN/interaction/relation.md +151 -0
- package/guide/zh_CN/interaction/slot.md +137 -0
- package/guide/zh_CN/interaction/template_import.md +94 -0
- package/guide/zh_CN/interaction/trait_behavior.md +117 -0
- package/guide/zh_CN/styling/external_class.md +46 -0
- package/guide/zh_CN/styling/style_isolation.md +54 -0
- package/guide/zh_CN/styling/virtual_host.md +52 -0
- package/guide/zh_CN/tree/element_iterator.md +54 -0
- package/guide/zh_CN/tree/mutation_observer.md +52 -0
- package/guide/zh_CN/tree/node_tree.md +142 -0
- package/guide/zh_CN/tree/node_tree_modification.md +78 -0
- package/guide/zh_CN/tree/selector.md +66 -0
- package/jest.config.js +6 -0
- package/jest.dts.config.js +9 -0
- package/jest.unit.config.js +14 -0
- package/package.json +28 -0
- package/src/backend/backend_protocol.ts +313 -0
- package/src/backend/composed_backend_protocol.ts +252 -0
- package/src/backend/domlike_backend_protocol.ts +370 -0
- package/src/backend/mode.ts +51 -0
- package/src/backend/suggested_backend_protocol.ts +83 -0
- package/src/behavior.ts +1655 -0
- package/src/bootstrap_dom_dev.js +22 -0
- package/src/class_list.ts +376 -0
- package/src/component.ts +1309 -0
- package/src/component_params.ts +461 -0
- package/src/component_space.ts +547 -0
- package/src/data_path.ts +225 -0
- package/src/data_proxy.ts +670 -0
- package/src/data_utils.ts +50 -0
- package/src/element.ts +1966 -0
- package/src/element_iterator.ts +158 -0
- package/src/event.ts +401 -0
- package/src/external_shadow_tree.ts +27 -0
- package/src/func_arr.ts +198 -0
- package/src/global_options.ts +242 -0
- package/src/index.ts +187 -0
- package/src/mutation_observer.ts +252 -0
- package/src/native_node.ts +74 -0
- package/src/node.ts +174 -0
- package/src/relation.ts +380 -0
- package/src/render.ts +25 -0
- package/src/selector.ts +218 -0
- package/src/shadow_root.ts +766 -0
- package/src/template_engine.ts +45 -0
- package/src/text_node.ts +149 -0
- package/src/tmpl/index.ts +199 -0
- package/src/tmpl/native_rendering.ts +175 -0
- package/src/tmpl/proc_gen_wrapper.ts +954 -0
- package/src/tmpl/proc_gen_wrapper_dom.ts +230 -0
- package/src/tmpl/range_list_diff.ts +443 -0
- package/src/trait_behaviors.ts +51 -0
- package/src/virtual_node.ts +51 -0
- package/tests/backend/domlike.test.ts +254 -0
- package/tests/base/env.ts +78 -0
- package/tests/base/match.ts +185 -0
- package/tests/core/backend.test.ts +144 -0
- package/tests/core/behavior.test.ts +546 -0
- package/tests/core/component_space.test.ts +212 -0
- package/tests/core/data_update.test.ts +461 -0
- package/tests/core/misc.test.ts +339 -0
- package/tests/core/placeholder.test.ts +180 -0
- package/tests/core/slot.test.ts +1495 -0
- package/tests/core/trait_behaviors.test.ts +153 -0
- package/tests/legacy/README.md +3 -0
- package/tests/legacy/behavior.test.js +293 -0
- package/tests/legacy/component.test.js +1247 -0
- package/tests/legacy/data_path.test.js +149 -0
- package/tests/legacy/data_proxy.test.js +759 -0
- package/tests/legacy/element_iterator.test.js +148 -0
- package/tests/legacy/event.test.js +849 -0
- package/tests/legacy/external.test.js +510 -0
- package/tests/legacy/extra_info.test.js +109 -0
- package/tests/legacy/generics.test.js +176 -0
- package/tests/legacy/mutation_observer.test.js +210 -0
- package/tests/legacy/relation.test.js +517 -0
- package/tests/legacy/selector.test.js +263 -0
- package/tests/legacy/slot.test.js +915 -0
- package/tests/legacy/virtual.test.js +394 -0
- package/tests/tmpl/binding_map.test.ts +208 -0
- package/tests/tmpl/event.test.ts +206 -0
- package/tests/tmpl/expression.test.ts +429 -0
- package/tests/tmpl/lvalue.test.ts +160 -0
- package/tests/tmpl/native_rendering.test.ts +155 -0
- package/tests/tmpl/structure.test.ts +998 -0
- package/tests/types/chaining.test.ts +614 -0
- package/tests/types/createElement.test.ts +82 -0
- package/tests/types/definition.test.ts +442 -0
- package/tsconfig.json +11 -0
- package/webpack.config.js +270 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
# glass-easel 的模板处理
|
|
2
|
+
|
|
3
|
+
从整体模型角度上看, glass-easel 的作用接近于 web 上的 react 、 vue 等定义式框架。虽然很多设计细节与其他框架大为不同(例如, glass-easel 没有传统意义上的虚拟 DOM 树),但它需要面对很多问题仍然具有一定的相似性。
|
|
4
|
+
|
|
5
|
+
glass-easel 的基本任务之一是维护 WXML 模板,并将数据套用在模板上,生成 DOM 树;当数据被 `setData` 调用改变时,需要更新对应的 DOM 树内容。
|
|
6
|
+
|
|
7
|
+
## 模板更新基本原理
|
|
8
|
+
|
|
9
|
+
从更新原理上看, glass-easel 主要通过在预编译模板时分析得到的表达式和 setData 时改变的数据字段名,来确定 DOM 树中的哪些部分需要更新。例如,对于以下模板:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<view class="{{someClass}}"></view>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
如果执行以下 `setData` 调用:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
this.setData({
|
|
19
|
+
someClass: 'my-class',
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
则 `view` 节点的 `class` 可能会被更新。反之,如果执行:
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
this.setData({
|
|
27
|
+
another: 'something',
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
则 `view` 节点的 `class` 不可能被更新。
|
|
32
|
+
|
|
33
|
+
简而言之, glass-easel 可以预先知道 DOM 树中的某个位置可不可能被更新,并在必要时利用这一信息。
|
|
34
|
+
|
|
35
|
+
## 列表 diff 问题
|
|
36
|
+
|
|
37
|
+
然而,在遇到 `wx:for` 和列表数据时,不能仅依靠这种判断。例如,对于以下模板:
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<block wx:for="{{list}}" wx:key="{{key}}">
|
|
41
|
+
<view>{{item.value}}</view>
|
|
42
|
+
</block>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
如果原始数据是:
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
{
|
|
49
|
+
list: [
|
|
50
|
+
{ key: 'a', value: 'A' },
|
|
51
|
+
{ key: 'b', value: 'B' },
|
|
52
|
+
{ key: 'c', value: 'C' },
|
|
53
|
+
{ key: 'd', value: 'D' },
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
在此基础上执行 `setData` 调用:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
this.setData({
|
|
62
|
+
list: [
|
|
63
|
+
{ key: 'c', value: 'C' },
|
|
64
|
+
{ key: 'a', value: 'A' },
|
|
65
|
+
{ key: 'b', value: 'B' },
|
|
66
|
+
],
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
观察这个列表变化,可发现其实只是把原本列表中的倒数第二项移动到了列表头,并删除了原本列表中的最后一项。
|
|
71
|
+
|
|
72
|
+
这就要求框架: **根据两个数组的内容,归纳出一组合适的移动、创建、删除操作,并使得操作总数尽可能少。**
|
|
73
|
+
|
|
74
|
+
## 对 key 的应用
|
|
75
|
+
|
|
76
|
+
通常,框架会建议在列表中设置一个 `key` 字符串字段,以便框架快速找出各个项目之间的对应关系。例如对于以下这组数据变化:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
[ { key: 'a', value: 'A' }, { key: 'b', value: 'B' }, { key: 'c', value: 'C' } ]
|
|
80
|
+
// 变为
|
|
81
|
+
[ { key: 'b', value: 'B' }, { key: 'c', value: 'C2' }, { key: 'a', value: 'A' } ]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
框架会将操作归纳为:将 `key: 'a'` 的这一项移动到末尾。至于 `value` 发生的变化,框架此时并不会关心。
|
|
85
|
+
|
|
86
|
+
在实践中,这通常是一个高效的策略:只要 `key` 是具有足够代表性的信息,如用户 ID 、身份证号等,就可以用很低的开销来确定两个列表中各项之间的对应关系。
|
|
87
|
+
|
|
88
|
+
这样,整个问题可以进一步被简化为: **对两个给定的 key 数组,归纳出一组合适的移动、创建、删除操作,并使得操作总数尽可能少。**
|
|
89
|
+
|
|
90
|
+
例如:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
old: a, b, c
|
|
94
|
+
new: b, c, a
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
应归纳出操作:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
move: a -> 2
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
下文都将按照上述表述方法来举例说明。
|
|
104
|
+
|
|
105
|
+
# glass-easel list-diff 算法
|
|
106
|
+
|
|
107
|
+
glass-easel 使用的 list-diff 算法就是基于上述问题设计的。它可以确保操作总数最少,同时,在常规情况下的时间复杂度为 `O(N)` ,最差情况下的算法时间复杂度为 `O(NlogN)` ,并尽可能减少低效的 JS 操作。
|
|
108
|
+
|
|
109
|
+
## 预处理: key 唯一化
|
|
110
|
+
|
|
111
|
+
算法需要将列表数据转为 key 数组。为了方便比较,所有 key 都被转换为字符串类型。
|
|
112
|
+
|
|
113
|
+
此时,可能会产生列表中有重复 key 的情况。这种情况被认为是不合法的,会生成一条警告信息,但算法仍尝试去处理它——重复的 key 会被加上不同的后缀,以便区分它们。例如:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
new: a, b, b
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
这个列表中有重复 key ,会被进一步转换为:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
new: a, b--0, b--1
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
(如果碰巧有其他 key 是 `XX--NN` 的形式,则在生成后缀时也会避开它们。)
|
|
126
|
+
|
|
127
|
+
在检测重复 key 的过程中,会同时生成一个 key 到数组 index 的映射表 `newKeyMap` ;在上一次更新过程中,也有一个同样的表被保留下来,记为 `oldKeyMap` 。例如,对于更新过程:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
old: a, b, c
|
|
131
|
+
new: a, b--0, b--1
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
对应的 `oldKeyMap` 和 `newKeyMap` 分别为:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
oldKeyMap: { a: 0, b: 1, c: 2 }
|
|
138
|
+
newKeyMap: { a: 0, b--0: 1, b--1: 2 }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
(映射表查询和更新的时间复杂度可视为哈希表的时间复杂度,即 `O(1)` 。但其本身仍是相对较高开销的操作,所以整个算法中也会尽量减少此类操作。)
|
|
142
|
+
|
|
143
|
+
此外,如果没有指定 `wx:key` ,则不会进行上述步骤(会进入 **快速比较** 子算法)。
|
|
144
|
+
|
|
145
|
+
## 快速比较
|
|
146
|
+
|
|
147
|
+
在进入常规算法流程前, glass-easel 会检查当前情况是否适用于快速比较子算法。以下情况中,如满足任意一种,就适用这个子算法:
|
|
148
|
+
|
|
149
|
+
* 没有指定 `wx:key` ;
|
|
150
|
+
* glass-easel 预先判定出此次更新并没有更新列表数据,或者仅仅更新了列表数据中的非 key 字段;
|
|
151
|
+
* glass-easel 预先判定出此次更新只是在列表末尾追加项目。
|
|
152
|
+
|
|
153
|
+
快速比较子算法认为,此时是不会有列表中的已有项目发生移动的,只可能在列表的末尾发生追加或删除。
|
|
154
|
+
|
|
155
|
+
具体步骤可用伪代码表示如下:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
fastCompare:
|
|
159
|
+
for i = 0 ~ min(old.length, new.length)
|
|
160
|
+
matchChild(old[i], new[i])
|
|
161
|
+
if old.length > new.length
|
|
162
|
+
for i = new.length ~ old.length
|
|
163
|
+
removeChild(old[i]) // 移除 old 中多余的项目
|
|
164
|
+
if old.length < new.length
|
|
165
|
+
for i = old.length ~ new.length
|
|
166
|
+
appendChild(new[i]) // 追加 new 中多出来的项目
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
只要应用了快速比较子算法,就不用执行后续其他的子算法了。
|
|
170
|
+
|
|
171
|
+
需要注意的是,如果没有指定 `wx:key` ,虽然本次 list-diff 算法本身比较快,但如果在列表中间插入或删除项目,往往对整个过程的性能具有很大的负面影响。因此,不指定的 `wx:key` 的用法,主要适用于列表项目恒定不变,或者仅在列表末尾增删项目的情况,而不适用于其他情况。
|
|
172
|
+
|
|
173
|
+
## 最长升子序列算法
|
|
174
|
+
|
|
175
|
+
不适用快速比较子算法的情况下,会进入后续的常规算法流程。
|
|
176
|
+
|
|
177
|
+
要想使移动、创建、删除操作尽可能少,因为需要创建、删除的项目的数量是固定的(即 new 中特有的项目和 old 中特有的项目数量),所以算法的核心是使得移动操作尽可能少。
|
|
178
|
+
|
|
179
|
+
换而言之,就是使得在 old 和 new 中都有的项目中,不需要移动的项目尽可能多。例如:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
old: a, b, c, d, e
|
|
183
|
+
new: a, d, b, c, e
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
可以观察发现, `a` `b` `c` `e` 都不需要移动,只有 `d` 需要移动。找出一种做法使得不需要移动的项目最多就行了。这个问题非常类似于经典的最长公共子序列( longest common subsequence )问题。
|
|
187
|
+
|
|
188
|
+
然而,如果将列表中的每一项都使用 `oldKeyMap` 来替换为 old 中的 index ,则可以变为:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
oldId: 0, 1, 2, 3, 4
|
|
192
|
+
newId: 0, 3, 1, 4, 2
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
这样, `0` `1` `2` 都不需要移动,只有 `3` `4` 需要移动。实际上问题变成:在 new 表中找到一个递增的子序列,并使之尽可能长。这就是经典的最长递增子序列( longest increasing subsequence )问题。
|
|
196
|
+
|
|
197
|
+
这个问题有一个经典的 `O(NlogN)` 解法:在以下标 i 依次扫描 new 表各个项目的同时,维护一个 `minIndexByLen[j]` 数组,表示在所有长度为 j 的子序列中,它们的末项最小可以是多少。
|
|
198
|
+
|
|
199
|
+
例如,在 i = 1 (只考虑 new 列表的前 2 项)时,它的长度为 1 的子序列有 `0` 和 `3` ,其中较小的一个是 `0` ,所以 `minIndexByLen[1] = 0` 。它的长度为 2 的子序列只有 `0, 3` ,末项是 `3` ,所以 `minIndexByLen[2] = 3` 。同时 `minIndexByLen[3] = inf` 即没有 3 长度的子序列。
|
|
200
|
+
|
|
201
|
+
当进一步考虑 i = 2 ,即考虑 new 表的第 3 项时,长度为 2 的升子序列有 `0, 3` 和 `0, 1` ,其中较小的末项是 `1` ,所以更新 `minIndexByLen[2] = 1` 。
|
|
202
|
+
|
|
203
|
+
可以发现, `minIndexByLen` 一定是一个递增数组。用反证法可证,若有 `minIndexByLen[k] >= minIndexByLen[k + 1]` ,即有一个 k 长度的升子序列末项比一个 k + 1 长度的升子序列末项要大,那么后者的前 k 项一定是一个比前者更小的升子序列,矛盾。
|
|
204
|
+
|
|
205
|
+
更进一步地, i 每增大 1 ,即将 `newId[i]` 计入时,需要新考虑的升子序列的末项一定是 `newId[i]` ,那么,更新 `minIndexByLen` 时,只可能将数组项的值更新为 `newId[i]` ;又因 `minIndexByLen` 的单调性,此次更新只可能更新 `minIndexByLen` 的其中一项。
|
|
206
|
+
|
|
207
|
+
这样, i 每增大 1 ,就找到最小的 j 使其满足 `newId[i] < minIndexByLen[j]` ,然后更新 `minIndexByLen[j] = newId[i]` 。因为 `minIndexByLen` 单调,这里可以用二分查找算法来找到这个 j 。
|
|
208
|
+
|
|
209
|
+
最终 `minIndexByLen` 的最后一个非 `inf` 项,就是最长升子序列可能达到的最大长度。
|
|
210
|
+
|
|
211
|
+
那么这个最长升子序列含有哪些项目呢?
|
|
212
|
+
|
|
213
|
+
可以考虑另一个数组 `minIndexByLenIndex[j]` 表示使得长度为 j 的最小升子序列末项对应的 i (即这个末项在 new 表中的位置)。每次更新 `minIndexByLen[j] = newId[i]` 时,更新 `minIndexByLenIndex[j] = i` 。
|
|
214
|
+
|
|
215
|
+
再用另一个数组 `minIndexPrev[i]` 表示当 `newId[i]` 作为长度 j 的子序列末项时,它之前的一项是什么。此时可以简单地取长度为 j - 1 子序列的末项作为倒数第二项。具体来说,每次更新 `minIndexByLen[j] = newId[i]` 时,更新 `minIndexPrev[i] = minIndexByLenIndex[j - 1]` 。这样,通过不断查找 `minIndexPrev` ,就可以连续找到倒数第二项、倒数第三项……直至找到整个子序列。
|
|
216
|
+
|
|
217
|
+
以例子表述整个过程的话:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
oldId: 0, 1, 2, 3, 4
|
|
221
|
+
newId: 0, 3, 1, 4, 2
|
|
222
|
+
|
|
223
|
+
// i = 0 时, newId[i] = 0
|
|
224
|
+
minIndexByLen: nil, 0, inf
|
|
225
|
+
minIndexByLenIndex: nil, 0
|
|
226
|
+
minIndexPrev: nil
|
|
227
|
+
|
|
228
|
+
// i = 1 时, newId[i] = 3
|
|
229
|
+
minIndexByLen: nil, 0, 3, inf
|
|
230
|
+
minIndexByLenIndex: nil, 0, 1
|
|
231
|
+
minIndexPrev: nil, 0
|
|
232
|
+
|
|
233
|
+
// i = 2 时, newId[i] = 1
|
|
234
|
+
minIndexByLen: nil, 0, 1, inf
|
|
235
|
+
minIndexByLenIndex: nil, 0, 2
|
|
236
|
+
minIndexPrev: nil, 0, 0
|
|
237
|
+
|
|
238
|
+
// i = 3 时, newId[i] = 4
|
|
239
|
+
minIndexByLen: nil, 0, 1, 4, inf
|
|
240
|
+
minIndexByLenIndex: nil, 0, 2, 3
|
|
241
|
+
minIndexPrev: nil, 0, 0, 2
|
|
242
|
+
|
|
243
|
+
// i = 4 时, newId[i] = 2
|
|
244
|
+
minIndexByLen: nil, 0, 1, 2, inf
|
|
245
|
+
minIndexByLenIndex: nil, 0, 2, 4
|
|
246
|
+
minIndexPrev: nil, 0, 0, 2, 2
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
找最长升子序列 `lcs[]` 时,首先取 `minIndexByLenIndex` 的末项作为最后一项,然后在 `minIndexPrev` 中依次找到它之前的各项。
|
|
250
|
+
|
|
251
|
+
整个求 `lcs[]` 的过程用伪代码可表示如下:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
calcLcsNaive:
|
|
255
|
+
minIndexByLen = []
|
|
256
|
+
prevMinIndexByLen = []
|
|
257
|
+
minIndexPrev = []
|
|
258
|
+
for i = 0 ~ newId.length
|
|
259
|
+
j = binarySearch(minIndexByLen, newId[i]) // 二分查找
|
|
260
|
+
minIndexByLen[j] = newId[i]
|
|
261
|
+
minIndexByLenIndex[j] = i
|
|
262
|
+
if j > 0
|
|
263
|
+
minIndexPrev[i] = minIndexByLenIndexes[j - 1]
|
|
264
|
+
else
|
|
265
|
+
minIndexPrev[i] = nil
|
|
266
|
+
lcs.length = minIndexByLen.length
|
|
267
|
+
i = lcsLength - 1
|
|
268
|
+
cur = minIndexByLenIndex[i]
|
|
269
|
+
while i >= 0
|
|
270
|
+
lcs[i] = cur
|
|
271
|
+
cur = minIndexPrev[cur]
|
|
272
|
+
i = i - 1
|
|
273
|
+
return lcs
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
整个过程中,对于每个 i 需要执行的最主要操作是一次二分查找,二分查找的时间复杂度是 `O(logN)` ,因而整体时间复杂度为 `O(NlogN)` 。
|
|
277
|
+
|
|
278
|
+
## 对常见情形的优化
|
|
279
|
+
|
|
280
|
+
在实践中,常见的情况是,每次列表更新其实都不会大改列表,只是对列表局部进行一些改变。
|
|
281
|
+
|
|
282
|
+
实例一,在瀑布流加载时,只会在列表末尾进行追加:
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
oldId: 1, 2, 3
|
|
286
|
+
newId: 1, 2, 3, nil, nil, nil
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
(这种情况有可能会应用快速比较子算法,也有可能不会。)
|
|
290
|
+
|
|
291
|
+
实例二,视频弹幕流更新时,总是删除一些头部,再追加一些尾部:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
oldId: 1, 2, 3, 4, 5
|
|
295
|
+
newId: 3, 4, 5, nil, nil
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
实例三,将移动列表中一项从其他位置移动到头部:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
oldId: 1, 2, 3, 4, 5
|
|
302
|
+
newId: 3, 1, 2, 4, 5
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
这些实例中,其实列表中的绝大部分都没有变化,如果都应用朴素的 calcLcsNaive 算法,在实际场景中并不是最优的。
|
|
306
|
+
|
|
307
|
+
仔细观察上面的几个实例可以发现,很多时候 `newId[i + 1]` 就刚好是 `newId[i] + 1` 。这样,对于每个 i ,可以先不进行二分查找,而是先检查一下 `newId[i + 1] == newId[i] + 1` 。如果成立,根据 `minIndexByLen` 的单调性,在 `minIndexByLen` 中 `newId[i + 1]` 一定紧跟在 `newId[i]` 之后。
|
|
308
|
+
|
|
309
|
+
常见情况下,上述假定只对个别 i 不成立,这样可以尽量避免二分查找算法,将整体时间复杂度进一步控制在 `O(N)` 。
|
|
310
|
+
|
|
311
|
+
优化后,求 `lcs[]` 的过程用伪代码可表示如下:
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
calcLcs:
|
|
315
|
+
minIndexByLen = []
|
|
316
|
+
prevMinIndexByLen = []
|
|
317
|
+
minIndexPrev = []
|
|
318
|
+
prevJ = -1
|
|
319
|
+
for i = 0 ~ newId.length
|
|
320
|
+
if i == 0 || newId[i] = newId[i - 1] + 1
|
|
321
|
+
// 满足假定即可避免二分查找
|
|
322
|
+
j = prevJ + 1
|
|
323
|
+
else
|
|
324
|
+
j = binarySearch(minIndexByLen, newId[i])
|
|
325
|
+
prevJ = j
|
|
326
|
+
minIndexByLen[j] = newId[i]
|
|
327
|
+
minIndexByLenIndex[j] = i
|
|
328
|
+
if j > 0
|
|
329
|
+
minIndexPrev[i] = minIndexByLenIndexes[j - 1]
|
|
330
|
+
else
|
|
331
|
+
minIndexPrev[i] = nil
|
|
332
|
+
lcs.length = minIndexByLen.length
|
|
333
|
+
i = lcsLength - 1
|
|
334
|
+
cur = minIndexByLenIndex[i]
|
|
335
|
+
while i >= 0
|
|
336
|
+
lcs[i] = cur
|
|
337
|
+
cur = minIndexPrev[cur]
|
|
338
|
+
i = i - 1
|
|
339
|
+
return lcs
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
此外,在实际实现时,并不需要将 `newId` 数组实际计算出来,而是在需要时,根据 `new` 和 `oldKeyMap` 来获取。这样可以进一步降低开销。
|
|
343
|
+
|
|
344
|
+
## 统计删除和移动操作
|
|
345
|
+
|
|
346
|
+
得到最长升子序列 `lcs` 后,所有不在 `lcs[]` 内的项目就是需要变更的。先将这些项目都统计出来:
|
|
347
|
+
|
|
348
|
+
具体过程用伪代码可表示如下:
|
|
349
|
+
|
|
350
|
+
```
|
|
351
|
+
getOps:
|
|
352
|
+
oldOp = [] // 对于 old 表中每一项应该进行的操作
|
|
353
|
+
newItems = [] // new 表中每一项在 old 表中的位置
|
|
354
|
+
lcsCur = 0
|
|
355
|
+
for i = 0 ~ newId.length
|
|
356
|
+
if newId[i] == nil
|
|
357
|
+
// 对于 old 表中不存在的项目,需要创建
|
|
358
|
+
newItems[i] = -1
|
|
359
|
+
else if i == lcs[lcsCur]
|
|
360
|
+
// 对于 lcs 中的项目,不进行操作
|
|
361
|
+
lcsCur = lcsCur + 1
|
|
362
|
+
oldOp[newId[i]] = NONE
|
|
363
|
+
newItems[i] = nil
|
|
364
|
+
else
|
|
365
|
+
// 其他项目都需要移动
|
|
366
|
+
oldOp[newId[i]] = MOVE
|
|
367
|
+
newItems[i] = newId[i]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
最后依次执行这些操作即可。用伪代码表示如下:
|
|
371
|
+
|
|
372
|
+
```
|
|
373
|
+
doOps:
|
|
374
|
+
oldCur = 0
|
|
375
|
+
newCur = 0
|
|
376
|
+
for i = 0 ~ lcs.length
|
|
377
|
+
while oldCur < newId[i]
|
|
378
|
+
if oldOp[oldCur] == nil
|
|
379
|
+
removeChild(old[oldCur])
|
|
380
|
+
while newCur < i
|
|
381
|
+
if newItems[i] == -1
|
|
382
|
+
insertChild(new[newCur], oldCur)
|
|
383
|
+
else
|
|
384
|
+
moveChild(new[newCur], oldCur)
|
|
385
|
+
matchChild(old[oldCur], new[newCur])
|
|
386
|
+
oldCur = newId[i] + 1
|
|
387
|
+
newCur = i + 1
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
这些步骤都是简单的数组操作,不会影响整体算法时间复杂度。
|
|
391
|
+
|
|
392
|
+
# 性能测试
|
|
393
|
+
|
|
394
|
+
以下是 glass-easel list-diff 整体算法的列表更新测试性能数据(对比小程序现有 list-diff 算法)。
|
|
395
|
+
|
|
396
|
+
| 测试项 | 现有 list-diff | glass-easel list-diff |
|
|
397
|
+
| ------ | --------------- | ---------------------- |
|
|
398
|
+
| 未变更 | 21.3ms | 11ms |
|
|
399
|
+
| 批量追加(模拟瀑布流加载) | 76.0ms | 71.6ms |
|
|
400
|
+
| 删头加尾(模拟弹幕滚动) | 19.1ms | 15.0ms |
|
|
401
|
+
| 单项移动 | 95.6ms | 10.1ms |
|
|
402
|
+
| 随机移动 | 146.1ms | 130.0ms |
|
|
403
|
+
|
|
404
|
+
可见, glass-easel list-diff 总体表现好于原有算法。其中,列表未变化或仅有少量变化时,可以带来巨大的性能提升。
|
|
405
|
+
|
|
406
|
+
除了自身性能之外, glass-easel list-diff 还带来了最优的项目移动次数、更易预测的项目变更顺序,这些都使得框架整体更友好和高效。
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# 入门
|
|
2
|
+
|
|
3
|
+
glass-easel 将整个页面视为由 **组件** 组成的,组件间可以相互引用。
|
|
4
|
+
|
|
5
|
+
其中一个组件用于展示完整页面内容,称为 **根组件** 。在一个最简单的页面中,可以只有一个根组件。
|
|
6
|
+
|
|
7
|
+
每个组件大体上由三部分内容组成:模板、样式、脚本。
|
|
8
|
+
|
|
9
|
+
## 模板及其编译
|
|
10
|
+
|
|
11
|
+
模板就是包含 `{{ ... }}` 数据绑定的类 XML 代码。 glass-easel 模板遵循 WXML 语法。例如:
|
|
12
|
+
|
|
13
|
+
```xml
|
|
14
|
+
<div class="blue">
|
|
15
|
+
<span>{{ hello }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
模板需要预先使用 `glass-easel-template-compiler` 编译。这个编译器既可以通过 WebAssembly 的方式引入,也可以在构建期间调用。
|
|
20
|
+
|
|
21
|
+
如果是以 WebAssembly 方式引入,基本的调用方法是:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { TmplGroup } from 'glass-easel-template-compiler'
|
|
25
|
+
const compileTemplate = (src: string) => {
|
|
26
|
+
const group = new TmplGroup()
|
|
27
|
+
group.addTmpl('', src)
|
|
28
|
+
const genObjectSrc = `return ${group.getTmplGenObjectGroups()}`
|
|
29
|
+
group.free()
|
|
30
|
+
const genObjectGroupList = (new Function(genObjectSrc))() as { [key: string]: any }
|
|
31
|
+
return {
|
|
32
|
+
content: genObjectGroupList[''],
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 样式
|
|
38
|
+
|
|
39
|
+
样式就是一段 CSS 代码。例如:
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
.blue {
|
|
43
|
+
color: blue;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
glass-easel 本身并不对 CSS 做过多处理,直接将其引入页面即可。
|
|
48
|
+
|
|
49
|
+
## 脚本
|
|
50
|
+
|
|
51
|
+
组件需要用一段 JavaScript 或 TypeScript 代码来定义出来。例如:
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
// hello-world.js
|
|
55
|
+
import * as glassEasel from 'glass-easel'
|
|
56
|
+
|
|
57
|
+
// 定义一个组件空间
|
|
58
|
+
const componentSpace = new glassEasel.ComponentSpace()
|
|
59
|
+
|
|
60
|
+
// 组件模板
|
|
61
|
+
const template = `
|
|
62
|
+
<div class="blue">
|
|
63
|
+
<span>{{ hello }}</span>
|
|
64
|
+
</div>
|
|
65
|
+
`
|
|
66
|
+
|
|
67
|
+
// 定义组件
|
|
68
|
+
export const helloWorld = componentSpace.defineComponent({
|
|
69
|
+
// 组件所使用的模板
|
|
70
|
+
template: compileTemplate(template),
|
|
71
|
+
// 用在组件模板上的数据
|
|
72
|
+
data: {
|
|
73
|
+
hello: 'Hello world!'
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 挂载
|
|
79
|
+
|
|
80
|
+
最后,在 DOM 环境下,需要一段启动脚本来将根组件 **挂载** 到页面内。例如:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
import * as glassEasel from 'glass-easel'
|
|
84
|
+
import { helloWorld } from './hello-world'
|
|
85
|
+
|
|
86
|
+
// 创建根组件实例
|
|
87
|
+
const domBackend = new glassEasel.domlikeBackend.CurrentWindowBackendContext()
|
|
88
|
+
const rootComponent = glassEasel.Component.createWithContext('body', helloWorld, domBackend)
|
|
89
|
+
|
|
90
|
+
// 将组件插入到 DOM 树中
|
|
91
|
+
const placeholder = document.createElement('span')
|
|
92
|
+
document.body.appendChild(placeholder)
|
|
93
|
+
glassEasel.Element.replaceDocumentElement(rootComponent, document.body, placeholder)
|
|
94
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# 组件
|
|
2
|
+
|
|
3
|
+
## API 风格
|
|
4
|
+
|
|
5
|
+
在定义组件时,有两种接口风格可选: **Definition API** 和 **Chaining API** 。
|
|
6
|
+
|
|
7
|
+
### Definition API
|
|
8
|
+
|
|
9
|
+
Definition API 类似于微信小程序的传统接口风格,例如:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
export const helloWorld = componentSpace.defineComponent({
|
|
13
|
+
template: compileTemplate(template),
|
|
14
|
+
data: {
|
|
15
|
+
hello: 'Hello world!',
|
|
16
|
+
},
|
|
17
|
+
methods: {},
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
这种风格的代码比较符合传统习惯,但对 TypeScript 的支持有一定限制。
|
|
22
|
+
|
|
23
|
+
### Chaining API
|
|
24
|
+
|
|
25
|
+
Chaining API 采用连缀的形式串联各个字段,例如:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
export const helloWorld = componentSpace.define()
|
|
29
|
+
.template(compileTemplate(template))
|
|
30
|
+
.data(() => ({
|
|
31
|
+
hello: 'Hello world!',
|
|
32
|
+
}))
|
|
33
|
+
.registerComponent()
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
这种风格的特点是连缀函数(除了 `template` )可以调用多次,这样可以轻松地将一个组件的逻辑分为几个部分,对于大组件的逻辑解耦有一定帮助。
|
|
37
|
+
|
|
38
|
+
### 混用 API 风格
|
|
39
|
+
|
|
40
|
+
这两种接口风格是等价的,定义每个组件时都可以任选一种。
|
|
41
|
+
|
|
42
|
+
在同一个组件内, Definition API 也可以混用于 Chaining API 中,例如:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
export const helloWorld = componentSpace.define()
|
|
46
|
+
.definition({
|
|
47
|
+
template: compileTemplate(template),
|
|
48
|
+
data: {
|
|
49
|
+
hello: 'Hello world!',
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
.registerComponent()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 引用其他组件
|
|
56
|
+
|
|
57
|
+
组件可以引用其他组件。例如有另一个组件:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
export const myComponent = componentSpace.defineComponent({
|
|
61
|
+
template: compileTemplate(`
|
|
62
|
+
<div> Some text in myComponent </div>
|
|
63
|
+
`),
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
引入组件:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
// 使用 Definition API 引入组件
|
|
71
|
+
export const helloWorld = componentSpace.defineComponent({
|
|
72
|
+
using: {
|
|
73
|
+
'my-component': myComponent,
|
|
74
|
+
},
|
|
75
|
+
template: compileTemplate(`
|
|
76
|
+
<my-component />
|
|
77
|
+
`),
|
|
78
|
+
})
|
|
79
|
+
// 或使用 Chaining API 引入组件
|
|
80
|
+
export const helloWorld = componentSpace.define()
|
|
81
|
+
.usingComponents({
|
|
82
|
+
'my-component': myComponent,
|
|
83
|
+
})
|
|
84
|
+
.template(compileTemplate(`
|
|
85
|
+
<my-component />
|
|
86
|
+
`))
|
|
87
|
+
.registerComponent()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 属性
|
|
91
|
+
|
|
92
|
+
属性是组件对外暴露的数据字段,组件的使用者可以通过模板来指定组件的属性值。
|
|
93
|
+
|
|
94
|
+
定义属性:
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
// 使用 Definition API 定义属性
|
|
98
|
+
export const addComponent = componentSpace.defineComponent({
|
|
99
|
+
template: compileTemplate(`
|
|
100
|
+
<div>{{a}} + {{b}} = {{ a + b }}</div>
|
|
101
|
+
`),
|
|
102
|
+
properties: {
|
|
103
|
+
a: Number, // 数值类型
|
|
104
|
+
b: {
|
|
105
|
+
type: Number, // 数值类型
|
|
106
|
+
value: 1, // 初始属性值为 1
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
// 或使用 Chaining API 定义属性
|
|
111
|
+
export const addComponent = componentSpace.define()
|
|
112
|
+
.template(compileTemplate(`
|
|
113
|
+
<div>{{a}} + {{b}} = {{ a + b }}</div>
|
|
114
|
+
`)
|
|
115
|
+
.property('a', Number)
|
|
116
|
+
.property('b', {
|
|
117
|
+
type: Number,
|
|
118
|
+
value: 1,
|
|
119
|
+
})
|
|
120
|
+
.registerComponent()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
使用组件时可以指定属性:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
export const helloWorld = componentSpace.defineComponent({
|
|
127
|
+
using: {
|
|
128
|
+
'my-component': myComponent,
|
|
129
|
+
},
|
|
130
|
+
template: compileTemplate(`
|
|
131
|
+
<my-component a="1" b="2" />
|
|
132
|
+
`),
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## init 函数
|
|
137
|
+
|
|
138
|
+
init 函数是可以使用 Chaining API 定义的函数,每次创建实例时,函数会被执行一次。
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
export const helloWorld = componentSpace.define()
|
|
142
|
+
.template(compileTemplate(`
|
|
143
|
+
<div>{{ hello }}</div>
|
|
144
|
+
`))
|
|
145
|
+
.data(() => ({
|
|
146
|
+
hello: 'Hello world!',
|
|
147
|
+
}))
|
|
148
|
+
.init(function ({ setData }) {
|
|
149
|
+
// 这个函数在每次创建实例时会执行一次
|
|
150
|
+
// 不在模板上使用的私有变量可以定义在这里
|
|
151
|
+
const someInnerData = {}
|
|
152
|
+
})
|
|
153
|
+
.registerComponent()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
在这个函数中,可以定义私有变量和方法等,不需要用在模板上的变量可以定义在其中。函数的第一个参数中也提供了一些实用的工具方法。
|