subay 0.0.12 → 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.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
- 因为模板是 HTML 字符串,引用的时候不支持自闭合标签。
608
- 传给组件的参数必须和组件声明的参数顺序一致。
609
- 组件获得的参数总是和传入的保持一致。
610
-
611
- **例子**
612
-
613
- ```typescript
614
- import { html, o } from 'subay';
615
-
616
- const Greeting = (greet: string, name: () => string) => {
617
- return html`<h1>${greet}, ${name}!</h1>`;
618
- };
619
-
620
- const name = o('World');
621
- const app = html` <div><${Greeting} greet=${'Hello'} name=${name}></${Greeting}></div> `;
622
-
623
- document.body.append(...app);
624
- ```
625
-
626
- **错误示例**
627
-
628
- ```typescript
629
- import { html, o } from 'subay';
630
-
631
- const Greeting = (greet: string, name: () => string) => {
632
- return html`<h1>${greet}, ${name}!</h1>`;
633
- };
634
-
635
- const name = o('World');
636
- // 错误, 后面的 span 会被当做 Greeting 的子节点。
637
- const app = html`
638
- <div>
639
- <${Greeting}
640
- greet=${'Hello'}
641
- name=${name}
642
- />
643
- <span></span>
644
- </div>
645
- `;
646
- // 错误, 参数顺序和 Greeting 的参数不一致
647
- const app = html` <div><${Greeting} name=${name} greet=${'Hi'} ></${Greeting}></div> `;
648
-
649
- document.body.append(...app);
650
- ```
651
-
652
- **动态的组件**
653
-
654
- 标签位置的插值可以是 `S` 或 `o` 的返回值,可以根据条件动态选择要渲染的组件。
655
-
656
- **例子**
657
-
658
- ```typescript
659
- import { html, o, S } from 'subay';
660
-
661
- const Greeting = (name: () => string) => {
662
- return html`<h1>Hello, ${name}!</h1>`;
663
- };
664
-
665
- const Farewell = (name: () => string) => {
666
- return html`<h1>Goodbye, ${name}!</h1>`;
667
- };
668
-
669
- const showGreeting = o(true);
670
- const name = o('World');
671
- const Component = S(() => (showGreeting() ? Greeting : Farewell));
672
-
673
- const app = html`
674
- <div>
675
- <${Component} name=${name} ></${Component}>
676
- <button onclick=${() => showGreeting(!showGreeting())}>Toggle</button>
677
- </div>
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
+ ```