subay 0.0.13 → 0.1.1
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 +14 -18
- package/README.zh.md +677 -682
- package/examples/todo-app.html +5 -20
- package/lib/arr.d.ts +2 -0
- package/lib/arr.d.ts.map +1 -0
- package/lib/arr.js +122 -0
- package/lib/arr.js.map +1 -0
- package/lib/h.d.ts.map +1 -1
- package/lib/h.js +136 -318
- package/lib/h.js.map +1 -1
- package/package.json +1 -1
- package/skills/subay-application/SKILL.md +122 -0
- package/skills/subay-component/SKILL.md +53 -0
- package/skills/subay-expert/SKILL.md +37 -0
- package/skills/subay-state/SKILL.md +162 -0
- package/skills/subay-template/SKILL.md +39 -0
- package/src/arr.ts +428 -0
- package/src/h.ts +144 -365
package/README.zh.md
CHANGED
|
@@ -1,682 +1,677 @@
|
|
|
1
|
-
# Subay
|
|
2
|
-
|
|
3
|
-
Subay 是一个轻量级的响应式编程库,提供了状态管理和 DOM 操作的功能。
|
|
4
|
-
|
|
5
|
-
## API
|
|
6
|
-
|
|
7
|
-
### root
|
|
8
|
-
|
|
9
|
-
**简介**
|
|
10
|
-
创建一个根更新上下文,用于管理响应式状态的生命周期。
|
|
11
|
-
|
|
12
|
-
**语法**
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
root<T>(fn: (destroy: () => void) => T): T
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**参数**
|
|
19
|
-
|
|
20
|
-
- `fn`: 一个函数,接收一个 `destroy` 函数作为参数,用于手动销毁根上下文。
|
|
21
|
-
|
|
22
|
-
**返回值**
|
|
23
|
-
|
|
24
|
-
- `T`: `fn` 函数的返回值。
|
|
25
|
-
|
|
26
|
-
**例子**
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { root, o, S } from 'subay';
|
|
30
|
-
|
|
31
|
-
root((destroy) => {
|
|
32
|
-
const count = o(0);
|
|
33
|
-
const doubled = S(() => count() * 2);
|
|
34
|
-
|
|
35
|
-
console.log(doubled()); // 0
|
|
36
|
-
count(1);
|
|
37
|
-
console.log(doubled()); // 2
|
|
38
|
-
|
|
39
|
-
// 手动销毁
|
|
40
|
-
destroy();
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**注意**
|
|
45
|
-
|
|
46
|
-
销毁使用 `root` 创建的上下文的唯一方式就是调用 `destroy` 回调。
|
|
47
|
-
|
|
48
|
-
### S
|
|
49
|
-
|
|
50
|
-
**简介**
|
|
51
|
-
创建一个计算值,当依赖的可观察值变化时自动重新计算。
|
|
52
|
-
|
|
53
|
-
**语法**
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
S<T>(fn: (pv: T | undefined) => T, value?: T): IS<T>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
**参数**
|
|
60
|
-
|
|
61
|
-
- `fn`: 计算函数,接收上一次的计算值作为参数。
|
|
62
|
-
- `value`: 可选的初始值。
|
|
63
|
-
|
|
64
|
-
**返回值**
|
|
65
|
-
|
|
66
|
-
- `IS<T>`: 一个函数,调用时返回计算值。
|
|
67
|
-
|
|
68
|
-
**例子**
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import { S, o } from 'subay';
|
|
72
|
-
|
|
73
|
-
const count = o(0);
|
|
74
|
-
const doubled = S(() => count() * 2);
|
|
75
|
-
|
|
76
|
-
console.log(doubled()); // 0
|
|
77
|
-
count(1);
|
|
78
|
-
console.log(doubled()); // 2
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### o / observable
|
|
82
|
-
|
|
83
|
-
**简介**
|
|
84
|
-
创建一个可观察值,用于存储和更新状态。
|
|
85
|
-
|
|
86
|
-
**语法**
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
o<T>(value: T): IO<T>
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
**参数**
|
|
93
|
-
|
|
94
|
-
- `value`: 初始值。
|
|
95
|
-
|
|
96
|
-
**返回值**
|
|
97
|
-
|
|
98
|
-
- `IO<T>`: 一个函数,无参数调用时返回当前值,带参数调用时更新值并返回新值。
|
|
99
|
-
|
|
100
|
-
**例子**
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { o } from 'subay';
|
|
104
|
-
|
|
105
|
-
const count = o(0);
|
|
106
|
-
console.log(count()); // 0
|
|
107
|
-
count(1);
|
|
108
|
-
console.log(count()); // 1
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### cleanup
|
|
112
|
-
|
|
113
|
-
**简介**
|
|
114
|
-
注册一个清理函数,当当前更新上下文被销毁时执行。
|
|
115
|
-
|
|
116
|
-
**语法**
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
cleanup<T extends () => void>(f: T): T
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**参数**
|
|
123
|
-
|
|
124
|
-
- `f`: 清理函数。
|
|
125
|
-
|
|
126
|
-
**返回值**
|
|
127
|
-
|
|
128
|
-
- `T`: 传入的清理函数。
|
|
129
|
-
|
|
130
|
-
**例子**
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
import { root, S, o, cleanup } from 'subay';
|
|
134
|
-
|
|
135
|
-
root(() => {
|
|
136
|
-
const count = o(0);
|
|
137
|
-
|
|
138
|
-
S(() => {
|
|
139
|
-
console.log(count());
|
|
140
|
-
cleanup(() => {
|
|
141
|
-
console.log('Cleanup called');
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
count(1);
|
|
146
|
-
});
|
|
147
|
-
// 输出: 0, 1, Cleanup called
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### transaction
|
|
151
|
-
|
|
152
|
-
**简介**
|
|
153
|
-
创建一个事务,批量更新可观察值,避免中间状态的计算。
|
|
154
|
-
|
|
155
|
-
**语法**
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
transaction<T>(f: () => T): T
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
**参数**
|
|
162
|
-
|
|
163
|
-
- `f`: 事务函数,在其中可以更新多个可观察值。
|
|
164
|
-
|
|
165
|
-
**返回值**
|
|
166
|
-
|
|
167
|
-
- `T`: `f` 函数的返回值。
|
|
168
|
-
|
|
169
|
-
**例子**
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
import { transaction, o, S } from 'subay';
|
|
173
|
-
|
|
174
|
-
const a = o(1);
|
|
175
|
-
const b = o(2);
|
|
176
|
-
const sum = S(() => a() + b());
|
|
177
|
-
|
|
178
|
-
S(() => console.log(sum())); // 3
|
|
179
|
-
|
|
180
|
-
transaction(() => {
|
|
181
|
-
a(2);
|
|
182
|
-
b(3);
|
|
183
|
-
});
|
|
184
|
-
// 输出: 5 (只计算一次)
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### sample
|
|
188
|
-
|
|
189
|
-
**简介**
|
|
190
|
-
在不建立依赖关系的情况下获取可观察值的值。
|
|
191
|
-
|
|
192
|
-
**语法**
|
|
193
|
-
|
|
194
|
-
```typescript
|
|
195
|
-
sample<T>(f: () => T): T
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**参数**
|
|
199
|
-
|
|
200
|
-
- `f`: 函数,在其中可以访问可观察值但不会建立依赖关系。
|
|
201
|
-
|
|
202
|
-
**返回值**
|
|
203
|
-
|
|
204
|
-
- `T`: `f` 函数的返回值。
|
|
205
|
-
|
|
206
|
-
**例子**
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import { sample, o, S } from 'subay';
|
|
210
|
-
|
|
211
|
-
const count = o(0);
|
|
212
|
-
let cachedValue;
|
|
213
|
-
|
|
214
|
-
S(() => {
|
|
215
|
-
// 不建立依赖关系
|
|
216
|
-
cachedValue = sample(() => count());
|
|
217
|
-
console.log(cachedValue);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
count(1); // 不会触发重新计算
|
|
221
|
-
console.log(cachedValue); // 0
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### isListening
|
|
225
|
-
|
|
226
|
-
**简介**
|
|
227
|
-
检查当前是否处于响应式监听状态。
|
|
228
|
-
|
|
229
|
-
**语法**
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
isListening(): boolean
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
**参数**
|
|
236
|
-
|
|
237
|
-
- 无
|
|
238
|
-
|
|
239
|
-
**返回值**
|
|
240
|
-
|
|
241
|
-
- `boolean`: 当前是否处于响应式监听状态。
|
|
242
|
-
|
|
243
|
-
**例子**
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
import { isListening, S } from 'subay';
|
|
247
|
-
|
|
248
|
-
console.log(isListening()); // false
|
|
249
|
-
|
|
250
|
-
S(() => {
|
|
251
|
-
console.log(isListening()); // true
|
|
252
|
-
});
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
### subscribe
|
|
256
|
-
|
|
257
|
-
**简介**
|
|
258
|
-
订阅一个函数,当依赖的可观察值变化时自动执行。
|
|
259
|
-
|
|
260
|
-
**语法**
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
subscribe(f: () => void): () => void
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**参数**
|
|
267
|
-
|
|
268
|
-
- `f`: 订阅函数。
|
|
269
|
-
|
|
270
|
-
**返回值**
|
|
271
|
-
|
|
272
|
-
- `() => void`: 取消订阅的函数。
|
|
273
|
-
|
|
274
|
-
**例子**
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
import { subscribe, o } from 'subay';
|
|
278
|
-
|
|
279
|
-
const count = o(0);
|
|
280
|
-
const unsubscribe = subscribe(() => {
|
|
281
|
-
console.log(count());
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
count(1); // 输出: 1
|
|
285
|
-
count(2); // 输出: 2
|
|
286
|
-
|
|
287
|
-
unsubscribe();
|
|
288
|
-
count(3); // 无输出
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### unsubscribe
|
|
292
|
-
|
|
293
|
-
**简介**
|
|
294
|
-
取消订阅一个函数。
|
|
295
|
-
|
|
296
|
-
**语法**
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
unsubscribe(f: () => void): void
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
**参数**
|
|
303
|
-
|
|
304
|
-
- `f`: 要取消订阅的函数。
|
|
305
|
-
|
|
306
|
-
**返回值**
|
|
307
|
-
|
|
308
|
-
- 无
|
|
309
|
-
|
|
310
|
-
**例子**
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
import { subscribe, unsubscribe, o } from 'subay';
|
|
314
|
-
|
|
315
|
-
const count = o(0);
|
|
316
|
-
const fn = () => console.log(count());
|
|
317
|
-
|
|
318
|
-
subscribe(fn);
|
|
319
|
-
count(1); // 输出: 1
|
|
320
|
-
|
|
321
|
-
unsubscribe(fn);
|
|
322
|
-
count(2); // 无输出
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### isObservable
|
|
326
|
-
|
|
327
|
-
**简介**
|
|
328
|
-
检查一个值是否是可观察值。
|
|
329
|
-
|
|
330
|
-
**语法**
|
|
331
|
-
|
|
332
|
-
```typescript
|
|
333
|
-
isObservable(o: any): o is IO<any>
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
**参数**
|
|
337
|
-
|
|
338
|
-
- `o`: 要检查的值。
|
|
339
|
-
|
|
340
|
-
**返回值**
|
|
341
|
-
|
|
342
|
-
- `boolean`: 是否是可观察值。
|
|
343
|
-
|
|
344
|
-
**例子**
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
import { isObservable, o, S } from 'subay';
|
|
348
|
-
|
|
349
|
-
const count = o(0);
|
|
350
|
-
const doubled = S(() => count() * 2);
|
|
351
|
-
|
|
352
|
-
console.log(isObservable(count)); // true
|
|
353
|
-
console.log(isObservable(doubled)); // false
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### isComputed
|
|
357
|
-
|
|
358
|
-
**简介**
|
|
359
|
-
检查一个值是否是计算值。
|
|
360
|
-
|
|
361
|
-
**语法**
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
isComputed(o: any): o is IS<any>
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
**参数**
|
|
368
|
-
|
|
369
|
-
- `o`: 要检查的值。
|
|
370
|
-
|
|
371
|
-
**返回值**
|
|
372
|
-
|
|
373
|
-
- `boolean`: 是否是计算值。
|
|
374
|
-
|
|
375
|
-
**例子**
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
import { isComputed, o, S } from 'subay';
|
|
379
|
-
|
|
380
|
-
const count = o(0);
|
|
381
|
-
const doubled = S(() => count() * 2);
|
|
382
|
-
|
|
383
|
-
console.log(isComputed(count)); // false
|
|
384
|
-
console.log(isComputed(doubled)); // true
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
### isReactive
|
|
388
|
-
|
|
389
|
-
**简介**
|
|
390
|
-
检查一个值是否是响应式值(可观察值或计算值)。
|
|
391
|
-
|
|
392
|
-
**语法**
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
isReactive(o: any): o is IO<any> | IS<any>
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
**参数**
|
|
399
|
-
|
|
400
|
-
- `o`: 要检查的值。
|
|
401
|
-
|
|
402
|
-
**返回值**
|
|
403
|
-
|
|
404
|
-
- `boolean`: 是否是响应式值。
|
|
405
|
-
|
|
406
|
-
**例子**
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
import { isReactive, o, S } from 'subay';
|
|
410
|
-
|
|
411
|
-
const count = o(0);
|
|
412
|
-
const doubled = S(() => count() * 2);
|
|
413
|
-
|
|
414
|
-
console.log(isReactive(count)); // true
|
|
415
|
-
console.log(isReactive(doubled)); // true
|
|
416
|
-
console.log(isReactive(42)); // false
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### map
|
|
420
|
-
|
|
421
|
-
**简介**
|
|
422
|
-
将一个数组映射为 DOM 节点数组,当数组变化时自动更新。
|
|
423
|
-
|
|
424
|
-
**语法**
|
|
425
|
-
|
|
426
|
-
```typescript
|
|
427
|
-
map<T>(array: () => T[], f: (item: T) => Node[]): Node[]
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
**参数**
|
|
431
|
-
|
|
432
|
-
- `array`: 一个返回数组的函数。
|
|
433
|
-
- `f`: 映射函数,将数组项转换为 DOM 节点数组。
|
|
434
|
-
|
|
435
|
-
**返回值**
|
|
436
|
-
|
|
437
|
-
- `Node[]`: DOM 节点数组。
|
|
438
|
-
|
|
439
|
-
**例子**
|
|
440
|
-
|
|
441
|
-
```typescript
|
|
442
|
-
import { map, o, html } from 'subay';
|
|
443
|
-
|
|
444
|
-
const items = o(['a', 'b', 'c']);
|
|
445
|
-
const list = map(
|
|
446
|
-
() => items(),
|
|
447
|
-
(item) => html`<li>${item}</li>`,
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
document.body.append(...list);
|
|
451
|
-
// 渲染: <li>a</li><li>b</li><li>c</li>
|
|
452
|
-
|
|
453
|
-
items(['a', 'b', 'c', 'd']);
|
|
454
|
-
// 自动更新: <li>a</li><li>b</li><li>c</li><li>d</li>
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
### svg
|
|
458
|
-
|
|
459
|
-
**简介**
|
|
460
|
-
创建 SVG 元素的模板标签函数。
|
|
461
|
-
|
|
462
|
-
**语法**
|
|
463
|
-
|
|
464
|
-
```typescript
|
|
465
|
-
svg(segments: TemplateStringsArray, ...values: any[]): Node[]
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
**参数**
|
|
469
|
-
|
|
470
|
-
- `segments`: 模板字符串的静态部分。
|
|
471
|
-
- `values`: 模板字符串的动态部分。
|
|
472
|
-
|
|
473
|
-
**返回值**
|
|
474
|
-
|
|
475
|
-
- `Node[]`: SVG 元素节点数组。
|
|
476
|
-
|
|
477
|
-
**例子**
|
|
478
|
-
|
|
479
|
-
```typescript
|
|
480
|
-
import { svg, o } from 'subay';
|
|
481
|
-
|
|
482
|
-
const radius = o(50);
|
|
483
|
-
const circle = svg`<circle cx="100" cy="100" r="${radius}" fill="red"/>`;
|
|
484
|
-
|
|
485
|
-
document.getElementById('svg').append(...circle);
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### html
|
|
489
|
-
|
|
490
|
-
**简介**
|
|
491
|
-
创建 HTML 元素的模板标签函数。
|
|
492
|
-
|
|
493
|
-
**语法**
|
|
494
|
-
|
|
495
|
-
```typescript
|
|
496
|
-
html(segments: TemplateStringsArray, ...values: any[]): Node[]
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
**参数**
|
|
500
|
-
|
|
501
|
-
- `segments`: 模板字符串的静态部分。
|
|
502
|
-
- `values`: 模板字符串的动态部分。
|
|
503
|
-
|
|
504
|
-
**返回值**
|
|
505
|
-
|
|
506
|
-
- `Node[]`: HTML 元素节点数组。
|
|
507
|
-
|
|
508
|
-
**例子**
|
|
509
|
-
|
|
510
|
-
```typescript
|
|
511
|
-
import { html, o } from 'subay';
|
|
512
|
-
|
|
513
|
-
const count = o(0);
|
|
514
|
-
const counter = html`
|
|
515
|
-
<div>
|
|
516
|
-
<p>Count: ${count}</p>
|
|
517
|
-
<button onclick=${() => count(count() + 1)}>Increment</button>
|
|
518
|
-
</div>
|
|
519
|
-
`;
|
|
520
|
-
|
|
521
|
-
document.body.append(...counter);
|
|
522
|
-
// 点击按钮会自动更新 count 的值
|
|
523
|
-
```
|
|
524
|
-
|
|
525
|
-
**使用 HTML**
|
|
526
|
-
|
|
527
|
-
Subay 使用 `DOMParser` 解析 HTML 模板, `html` 的参数必须是合法的 HTML 字符串,(同理, `svg` 的参数必须是合法的 SVG 字符串), 这是有别于 JSX 的地方。
|
|
528
|
-
|
|
529
|
-
**例子**
|
|
530
|
-
|
|
531
|
-
```typescript
|
|
532
|
-
import { html } from 'subay';
|
|
533
|
-
|
|
534
|
-
// 合法的 HTML 字符串
|
|
535
|
-
const validHtml = html`<div><p>Hello</p></div>`;
|
|
536
|
-
|
|
537
|
-
document.body.append(...validHtml);
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
**错误示例**
|
|
541
|
-
|
|
542
|
-
```typescript
|
|
543
|
-
import { html } from 'subay';
|
|
544
|
-
|
|
545
|
-
// 自闭合标签
|
|
546
|
-
const invalidHtml = html`<div>
|
|
547
|
-
<span />
|
|
548
|
-
<span />
|
|
549
|
-
</div>`; // 会导致解析成 <div><span><span></span></span></div>
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
**传入对象作为元素属性**
|
|
553
|
-
|
|
554
|
-
如果需要传入一个对象作为元素属性,可以使用 `...` 语法。
|
|
555
|
-
|
|
556
|
-
**语法**
|
|
557
|
-
|
|
558
|
-
```typescript
|
|
559
|
-
html`<tag ...${props}></tag>`;
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
**例子**
|
|
563
|
-
|
|
564
|
-
```typescript
|
|
565
|
-
import { html, o } from 'subay';
|
|
566
|
-
|
|
567
|
-
const props = o({
|
|
568
|
-
className: 'container',
|
|
569
|
-
style: 'color: red;',
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
const element = html`<div ...${props}></div>`;
|
|
573
|
-
|
|
574
|
-
document.body.append(...element);
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
**组件的声明**
|
|
578
|
-
|
|
579
|
-
组件是一个返回 Node\[] 的函数,可以接收参数。和 React、Vue 等不同,组件的参数是一个列表。
|
|
580
|
-
|
|
581
|
-
**语法**
|
|
582
|
-
|
|
583
|
-
```typescript
|
|
584
|
-
const Component = (text, children: () => Node[]) => {
|
|
585
|
-
return html`<div>${text}</div>`;
|
|
586
|
-
};
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
**例子**
|
|
590
|
-
|
|
591
|
-
```typescript
|
|
592
|
-
import { html } from 'subay';
|
|
593
|
-
|
|
594
|
-
const Greeting = (name: string, children: () => Node[]) => {
|
|
595
|
-
return html`
|
|
596
|
-
<div>
|
|
597
|
-
<h1>Hello, ${name}!</h1>
|
|
598
|
-
${children()}
|
|
599
|
-
</div>
|
|
600
|
-
`;
|
|
601
|
-
};
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
**组件的使用**
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
`;
|
|
679
|
-
|
|
680
|
-
document.body.append(...app);
|
|
681
|
-
```
|
|
682
|
-
|
|
1
|
+
# Subay
|
|
2
|
+
|
|
3
|
+
Subay 是一个轻量级的响应式编程库,提供了状态管理和 DOM 操作的功能。
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
### root
|
|
8
|
+
|
|
9
|
+
**简介**
|
|
10
|
+
创建一个根更新上下文,用于管理响应式状态的生命周期。
|
|
11
|
+
|
|
12
|
+
**语法**
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
root<T>(fn: (destroy: () => void) => T): T
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**参数**
|
|
19
|
+
|
|
20
|
+
- `fn`: 一个函数,接收一个 `destroy` 函数作为参数,用于手动销毁根上下文。
|
|
21
|
+
|
|
22
|
+
**返回值**
|
|
23
|
+
|
|
24
|
+
- `T`: `fn` 函数的返回值。
|
|
25
|
+
|
|
26
|
+
**例子**
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { root, o, S } from 'subay';
|
|
30
|
+
|
|
31
|
+
root((destroy) => {
|
|
32
|
+
const count = o(0);
|
|
33
|
+
const doubled = S(() => count() * 2);
|
|
34
|
+
|
|
35
|
+
console.log(doubled()); // 0
|
|
36
|
+
count(1);
|
|
37
|
+
console.log(doubled()); // 2
|
|
38
|
+
|
|
39
|
+
// 手动销毁
|
|
40
|
+
destroy();
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**注意**
|
|
45
|
+
|
|
46
|
+
销毁使用 `root` 创建的上下文的唯一方式就是调用 `destroy` 回调。
|
|
47
|
+
|
|
48
|
+
### S
|
|
49
|
+
|
|
50
|
+
**简介**
|
|
51
|
+
创建一个计算值,当依赖的可观察值变化时自动重新计算。
|
|
52
|
+
|
|
53
|
+
**语法**
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
S<T>(fn: (pv: T | undefined) => T, value?: T): IS<T>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**参数**
|
|
60
|
+
|
|
61
|
+
- `fn`: 计算函数,接收上一次的计算值作为参数。
|
|
62
|
+
- `value`: 可选的初始值。
|
|
63
|
+
|
|
64
|
+
**返回值**
|
|
65
|
+
|
|
66
|
+
- `IS<T>`: 一个函数,调用时返回计算值。
|
|
67
|
+
|
|
68
|
+
**例子**
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { S, o } from 'subay';
|
|
72
|
+
|
|
73
|
+
const count = o(0);
|
|
74
|
+
const doubled = S(() => count() * 2);
|
|
75
|
+
|
|
76
|
+
console.log(doubled()); // 0
|
|
77
|
+
count(1);
|
|
78
|
+
console.log(doubled()); // 2
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### o / observable
|
|
82
|
+
|
|
83
|
+
**简介**
|
|
84
|
+
创建一个可观察值,用于存储和更新状态。
|
|
85
|
+
|
|
86
|
+
**语法**
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
o<T>(value: T): IO<T>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**参数**
|
|
93
|
+
|
|
94
|
+
- `value`: 初始值。
|
|
95
|
+
|
|
96
|
+
**返回值**
|
|
97
|
+
|
|
98
|
+
- `IO<T>`: 一个函数,无参数调用时返回当前值,带参数调用时更新值并返回新值。
|
|
99
|
+
|
|
100
|
+
**例子**
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { o } from 'subay';
|
|
104
|
+
|
|
105
|
+
const count = o(0);
|
|
106
|
+
console.log(count()); // 0
|
|
107
|
+
count(1);
|
|
108
|
+
console.log(count()); // 1
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### cleanup
|
|
112
|
+
|
|
113
|
+
**简介**
|
|
114
|
+
注册一个清理函数,当当前更新上下文被销毁时执行。
|
|
115
|
+
|
|
116
|
+
**语法**
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
cleanup<T extends () => void>(f: T): T
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**参数**
|
|
123
|
+
|
|
124
|
+
- `f`: 清理函数。
|
|
125
|
+
|
|
126
|
+
**返回值**
|
|
127
|
+
|
|
128
|
+
- `T`: 传入的清理函数。
|
|
129
|
+
|
|
130
|
+
**例子**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { root, S, o, cleanup } from 'subay';
|
|
134
|
+
|
|
135
|
+
root(() => {
|
|
136
|
+
const count = o(0);
|
|
137
|
+
|
|
138
|
+
S(() => {
|
|
139
|
+
console.log(count());
|
|
140
|
+
cleanup(() => {
|
|
141
|
+
console.log('Cleanup called');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
count(1);
|
|
146
|
+
});
|
|
147
|
+
// 输出: 0, 1, Cleanup called
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### transaction
|
|
151
|
+
|
|
152
|
+
**简介**
|
|
153
|
+
创建一个事务,批量更新可观察值,避免中间状态的计算。
|
|
154
|
+
|
|
155
|
+
**语法**
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
transaction<T>(f: () => T): T
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**参数**
|
|
162
|
+
|
|
163
|
+
- `f`: 事务函数,在其中可以更新多个可观察值。
|
|
164
|
+
|
|
165
|
+
**返回值**
|
|
166
|
+
|
|
167
|
+
- `T`: `f` 函数的返回值。
|
|
168
|
+
|
|
169
|
+
**例子**
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { transaction, o, S } from 'subay';
|
|
173
|
+
|
|
174
|
+
const a = o(1);
|
|
175
|
+
const b = o(2);
|
|
176
|
+
const sum = S(() => a() + b());
|
|
177
|
+
|
|
178
|
+
S(() => console.log(sum())); // 3
|
|
179
|
+
|
|
180
|
+
transaction(() => {
|
|
181
|
+
a(2);
|
|
182
|
+
b(3);
|
|
183
|
+
});
|
|
184
|
+
// 输出: 5 (只计算一次)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### sample
|
|
188
|
+
|
|
189
|
+
**简介**
|
|
190
|
+
在不建立依赖关系的情况下获取可观察值的值。
|
|
191
|
+
|
|
192
|
+
**语法**
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
sample<T>(f: () => T): T
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**参数**
|
|
199
|
+
|
|
200
|
+
- `f`: 函数,在其中可以访问可观察值但不会建立依赖关系。
|
|
201
|
+
|
|
202
|
+
**返回值**
|
|
203
|
+
|
|
204
|
+
- `T`: `f` 函数的返回值。
|
|
205
|
+
|
|
206
|
+
**例子**
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { sample, o, S } from 'subay';
|
|
210
|
+
|
|
211
|
+
const count = o(0);
|
|
212
|
+
let cachedValue;
|
|
213
|
+
|
|
214
|
+
S(() => {
|
|
215
|
+
// 不建立依赖关系
|
|
216
|
+
cachedValue = sample(() => count());
|
|
217
|
+
console.log(cachedValue);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
count(1); // 不会触发重新计算
|
|
221
|
+
console.log(cachedValue); // 0
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### isListening
|
|
225
|
+
|
|
226
|
+
**简介**
|
|
227
|
+
检查当前是否处于响应式监听状态。
|
|
228
|
+
|
|
229
|
+
**语法**
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
isListening(): boolean
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**参数**
|
|
236
|
+
|
|
237
|
+
- 无
|
|
238
|
+
|
|
239
|
+
**返回值**
|
|
240
|
+
|
|
241
|
+
- `boolean`: 当前是否处于响应式监听状态。
|
|
242
|
+
|
|
243
|
+
**例子**
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
import { isListening, S } from 'subay';
|
|
247
|
+
|
|
248
|
+
console.log(isListening()); // false
|
|
249
|
+
|
|
250
|
+
S(() => {
|
|
251
|
+
console.log(isListening()); // true
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### subscribe
|
|
256
|
+
|
|
257
|
+
**简介**
|
|
258
|
+
订阅一个函数,当依赖的可观察值变化时自动执行。
|
|
259
|
+
|
|
260
|
+
**语法**
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
subscribe(f: () => void): () => void
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**参数**
|
|
267
|
+
|
|
268
|
+
- `f`: 订阅函数。
|
|
269
|
+
|
|
270
|
+
**返回值**
|
|
271
|
+
|
|
272
|
+
- `() => void`: 取消订阅的函数。
|
|
273
|
+
|
|
274
|
+
**例子**
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { subscribe, o } from 'subay';
|
|
278
|
+
|
|
279
|
+
const count = o(0);
|
|
280
|
+
const unsubscribe = subscribe(() => {
|
|
281
|
+
console.log(count());
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
count(1); // 输出: 1
|
|
285
|
+
count(2); // 输出: 2
|
|
286
|
+
|
|
287
|
+
unsubscribe();
|
|
288
|
+
count(3); // 无输出
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### unsubscribe
|
|
292
|
+
|
|
293
|
+
**简介**
|
|
294
|
+
取消订阅一个函数。
|
|
295
|
+
|
|
296
|
+
**语法**
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
unsubscribe(f: () => void): void
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**参数**
|
|
303
|
+
|
|
304
|
+
- `f`: 要取消订阅的函数。
|
|
305
|
+
|
|
306
|
+
**返回值**
|
|
307
|
+
|
|
308
|
+
- 无
|
|
309
|
+
|
|
310
|
+
**例子**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { subscribe, unsubscribe, o } from 'subay';
|
|
314
|
+
|
|
315
|
+
const count = o(0);
|
|
316
|
+
const fn = () => console.log(count());
|
|
317
|
+
|
|
318
|
+
subscribe(fn);
|
|
319
|
+
count(1); // 输出: 1
|
|
320
|
+
|
|
321
|
+
unsubscribe(fn);
|
|
322
|
+
count(2); // 无输出
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### isObservable
|
|
326
|
+
|
|
327
|
+
**简介**
|
|
328
|
+
检查一个值是否是可观察值。
|
|
329
|
+
|
|
330
|
+
**语法**
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
isObservable(o: any): o is IO<any>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**参数**
|
|
337
|
+
|
|
338
|
+
- `o`: 要检查的值。
|
|
339
|
+
|
|
340
|
+
**返回值**
|
|
341
|
+
|
|
342
|
+
- `boolean`: 是否是可观察值。
|
|
343
|
+
|
|
344
|
+
**例子**
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
import { isObservable, o, S } from 'subay';
|
|
348
|
+
|
|
349
|
+
const count = o(0);
|
|
350
|
+
const doubled = S(() => count() * 2);
|
|
351
|
+
|
|
352
|
+
console.log(isObservable(count)); // true
|
|
353
|
+
console.log(isObservable(doubled)); // false
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### isComputed
|
|
357
|
+
|
|
358
|
+
**简介**
|
|
359
|
+
检查一个值是否是计算值。
|
|
360
|
+
|
|
361
|
+
**语法**
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
isComputed(o: any): o is IS<any>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**参数**
|
|
368
|
+
|
|
369
|
+
- `o`: 要检查的值。
|
|
370
|
+
|
|
371
|
+
**返回值**
|
|
372
|
+
|
|
373
|
+
- `boolean`: 是否是计算值。
|
|
374
|
+
|
|
375
|
+
**例子**
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { isComputed, o, S } from 'subay';
|
|
379
|
+
|
|
380
|
+
const count = o(0);
|
|
381
|
+
const doubled = S(() => count() * 2);
|
|
382
|
+
|
|
383
|
+
console.log(isComputed(count)); // false
|
|
384
|
+
console.log(isComputed(doubled)); // true
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### isReactive
|
|
388
|
+
|
|
389
|
+
**简介**
|
|
390
|
+
检查一个值是否是响应式值(可观察值或计算值)。
|
|
391
|
+
|
|
392
|
+
**语法**
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
isReactive(o: any): o is IO<any> | IS<any>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**参数**
|
|
399
|
+
|
|
400
|
+
- `o`: 要检查的值。
|
|
401
|
+
|
|
402
|
+
**返回值**
|
|
403
|
+
|
|
404
|
+
- `boolean`: 是否是响应式值。
|
|
405
|
+
|
|
406
|
+
**例子**
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { isReactive, o, S } from 'subay';
|
|
410
|
+
|
|
411
|
+
const count = o(0);
|
|
412
|
+
const doubled = S(() => count() * 2);
|
|
413
|
+
|
|
414
|
+
console.log(isReactive(count)); // true
|
|
415
|
+
console.log(isReactive(doubled)); // true
|
|
416
|
+
console.log(isReactive(42)); // false
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### map
|
|
420
|
+
|
|
421
|
+
**简介**
|
|
422
|
+
将一个数组映射为 DOM 节点数组,当数组变化时自动更新。
|
|
423
|
+
|
|
424
|
+
**语法**
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
map<T>(array: () => T[], f: (item: T) => Node[]): Node[]
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**参数**
|
|
431
|
+
|
|
432
|
+
- `array`: 一个返回数组的函数。
|
|
433
|
+
- `f`: 映射函数,将数组项转换为 DOM 节点数组。
|
|
434
|
+
|
|
435
|
+
**返回值**
|
|
436
|
+
|
|
437
|
+
- `Node[]`: DOM 节点数组。
|
|
438
|
+
|
|
439
|
+
**例子**
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { map, o, html } from 'subay';
|
|
443
|
+
|
|
444
|
+
const items = o(['a', 'b', 'c']);
|
|
445
|
+
const list = map(
|
|
446
|
+
() => items(),
|
|
447
|
+
(item) => html`<li>${item}</li>`,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
document.body.append(...list);
|
|
451
|
+
// 渲染: <li>a</li><li>b</li><li>c</li>
|
|
452
|
+
|
|
453
|
+
items(['a', 'b', 'c', 'd']);
|
|
454
|
+
// 自动更新: <li>a</li><li>b</li><li>c</li><li>d</li>
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### svg
|
|
458
|
+
|
|
459
|
+
**简介**
|
|
460
|
+
创建 SVG 元素的模板标签函数。
|
|
461
|
+
|
|
462
|
+
**语法**
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
svg(segments: TemplateStringsArray, ...values: any[]): Node[]
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**参数**
|
|
469
|
+
|
|
470
|
+
- `segments`: 模板字符串的静态部分。
|
|
471
|
+
- `values`: 模板字符串的动态部分。
|
|
472
|
+
|
|
473
|
+
**返回值**
|
|
474
|
+
|
|
475
|
+
- `Node[]`: SVG 元素节点数组。
|
|
476
|
+
|
|
477
|
+
**例子**
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { svg, o } from 'subay';
|
|
481
|
+
|
|
482
|
+
const radius = o(50);
|
|
483
|
+
const circle = svg`<circle cx="100" cy="100" r="${radius}" fill="red"/>`;
|
|
484
|
+
|
|
485
|
+
document.getElementById('svg').append(...circle);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### html
|
|
489
|
+
|
|
490
|
+
**简介**
|
|
491
|
+
创建 HTML 元素的模板标签函数。
|
|
492
|
+
|
|
493
|
+
**语法**
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
html(segments: TemplateStringsArray, ...values: any[]): Node[]
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**参数**
|
|
500
|
+
|
|
501
|
+
- `segments`: 模板字符串的静态部分。
|
|
502
|
+
- `values`: 模板字符串的动态部分。
|
|
503
|
+
|
|
504
|
+
**返回值**
|
|
505
|
+
|
|
506
|
+
- `Node[]`: HTML 元素节点数组。
|
|
507
|
+
|
|
508
|
+
**例子**
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
import { html, o } from 'subay';
|
|
512
|
+
|
|
513
|
+
const count = o(0);
|
|
514
|
+
const counter = html`
|
|
515
|
+
<div>
|
|
516
|
+
<p>Count: ${count}</p>
|
|
517
|
+
<button onclick=${() => count(count() + 1)}>Increment</button>
|
|
518
|
+
</div>
|
|
519
|
+
`;
|
|
520
|
+
|
|
521
|
+
document.body.append(...counter);
|
|
522
|
+
// 点击按钮会自动更新 count 的值
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**使用 HTML**
|
|
526
|
+
|
|
527
|
+
Subay 使用 `DOMParser` 解析 HTML 模板, `html` 的参数必须是合法的 HTML 字符串,(同理, `svg` 的参数必须是合法的 SVG 字符串), 这是有别于 JSX 的地方。
|
|
528
|
+
|
|
529
|
+
**例子**
|
|
530
|
+
|
|
531
|
+
```typescript
|
|
532
|
+
import { html } from 'subay';
|
|
533
|
+
|
|
534
|
+
// 合法的 HTML 字符串
|
|
535
|
+
const validHtml = html`<div><p>Hello</p></div>`;
|
|
536
|
+
|
|
537
|
+
document.body.append(...validHtml);
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**错误示例**
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import { html } from 'subay';
|
|
544
|
+
|
|
545
|
+
// 自闭合标签
|
|
546
|
+
const invalidHtml = html`<div>
|
|
547
|
+
<span />
|
|
548
|
+
<span />
|
|
549
|
+
</div>`; // 会导致解析成 <div><span><span></span></span></div>
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
**传入对象作为元素属性**
|
|
553
|
+
|
|
554
|
+
如果需要传入一个对象作为元素属性,可以使用 `...` 语法。
|
|
555
|
+
|
|
556
|
+
**语法**
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
html`<tag ...${props}></tag>`;
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**例子**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
import { html, o } from 'subay';
|
|
566
|
+
|
|
567
|
+
const props = o({
|
|
568
|
+
className: 'container',
|
|
569
|
+
style: 'color: red;',
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
const element = html`<div ...${props}></div>`;
|
|
573
|
+
|
|
574
|
+
document.body.append(...element);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**组件的声明**
|
|
578
|
+
|
|
579
|
+
组件是一个返回 Node\[] 的函数,可以接收参数。和 React、Vue 等不同,组件的参数是一个列表。
|
|
580
|
+
|
|
581
|
+
**语法**
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
const Component = (text, children: () => Node[]) => {
|
|
585
|
+
return html`<div>${text}</div>`;
|
|
586
|
+
};
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**例子**
|
|
590
|
+
|
|
591
|
+
```typescript
|
|
592
|
+
import { html } from 'subay';
|
|
593
|
+
|
|
594
|
+
const Greeting = (name: string, children: () => Node[]) => {
|
|
595
|
+
return html`
|
|
596
|
+
<div>
|
|
597
|
+
<h1>Hello, ${name}!</h1>
|
|
598
|
+
${children()}
|
|
599
|
+
</div>
|
|
600
|
+
`;
|
|
601
|
+
};
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**组件的使用**
|
|
605
|
+
|
|
606
|
+
Subay 的组件不支持在模板的标签位置进行插值,而是通过直接调用或延迟调用的方式使用。
|
|
607
|
+
延迟调用指的是创建一个函数,该函数返回组件调用的结果。
|
|
608
|
+
|
|
609
|
+
**例子**
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
import { html, o } from 'subay';
|
|
613
|
+
|
|
614
|
+
const Greeting = (greet: string, name: () => string) => {
|
|
615
|
+
return html`<h1>${greet}, ${name}!</h1>`;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const name = o('World');
|
|
619
|
+
|
|
620
|
+
// 直接调用
|
|
621
|
+
const app1 = html` <div>${Greeting('Hello', name)}</div> `;
|
|
622
|
+
|
|
623
|
+
// 延迟调用
|
|
624
|
+
const app2 = html` <div>${() => Greeting('Hello', name)}</div> `;
|
|
625
|
+
|
|
626
|
+
document.body.append(...app1);
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**错误示例**
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import { html, o } from 'subay';
|
|
633
|
+
|
|
634
|
+
const Greeting = (greet: string, name: () => string) => {
|
|
635
|
+
return html`<h1>${greet}, ${name}!</h1>`;
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const name = o('World');
|
|
639
|
+
// 错误 - 在标签位置插值
|
|
640
|
+
const app = html`
|
|
641
|
+
<div>
|
|
642
|
+
<${Greeting} greet=${'Hello'} name=${name}></${Greeting}>
|
|
643
|
+
</div>
|
|
644
|
+
`;
|
|
645
|
+
|
|
646
|
+
document.body.append(...app);
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**动态的组件**
|
|
650
|
+
|
|
651
|
+
可以根据条件动态选择要渲染的组件,通过函数返回不同的组件。
|
|
652
|
+
|
|
653
|
+
**例子**
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import { html, o, S } from 'subay';
|
|
657
|
+
|
|
658
|
+
const Greeting = (name: () => string) => {
|
|
659
|
+
return html`<h1>Hello, ${name}!</h1>`;
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
const Farewell = (name: () => string) => {
|
|
663
|
+
return html`<h1>Goodbye, ${name}!</h1>`;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const showGreeting = o(true);
|
|
667
|
+
const name = o('World');
|
|
668
|
+
|
|
669
|
+
const app = html`
|
|
670
|
+
<div>
|
|
671
|
+
${() => (showGreeting() ? Greeting(name) : Farewell(name))}
|
|
672
|
+
<button onclick=${() => showGreeting(!showGreeting())}>Toggle</button>
|
|
673
|
+
</div>
|
|
674
|
+
`;
|
|
675
|
+
|
|
676
|
+
document.body.append(...app);
|
|
677
|
+
```
|