@lytjs/reactivity 6.5.0 → 6.7.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 CHANGED
@@ -1,327 +1,327 @@
1
- # @lytjs/reactivity
2
-
3
- > LytJS 响应式系统,提供 ref、reactive、computed、watch、Signal 等核心响应式原语
4
-
5
- ## 安装
6
-
7
- ```bash
8
- npm install @lytjs/reactivity
9
- ```
10
-
11
- ## 核心 API
12
-
13
- ### reactive / shallowReactive / readonly / shallowReadonly
14
-
15
- 创建响应式对象,支持深层/浅层响应和只读模式
16
-
17
- ```typescript
18
- import { reactive, shallowReactive, readonly, shallowReadonly } from '@lytjs/reactivity';
19
-
20
- const state = reactive({ count: 0 });
21
- state.count++; // 自动追踪
22
-
23
- const shallow = shallowReactive({ nested: { value: 1 } });
24
- shallow.nested.value = 2; // 不会触发更新(浅层响应)
25
- ```
26
-
27
- ### ref / shallowRef
28
-
29
- 创建响应式引用,适用于基本类型值
30
-
31
- ```typescript
32
- import { ref, shallowRef, isShallowRef } from '@lytjs/reactivity';
33
-
34
- const count = ref(0);
35
- count.value++;
36
-
37
- const shallow = shallowRef({ nested: { value: 1 } });
38
- // isShallowRef 类型守卫
39
- if (isShallowRef(shallow)) {
40
- console.log('这是一个 ShallowRef');
41
- }
42
- ```
43
-
44
- ### isShallowRef / isComputedRef
45
-
46
- 类型守卫函数,用于在运行时判断 Ref 类型
47
-
48
- ```typescript
49
- import { ref, shallowRef, computed, isShallowRef, isComputedRef } from '@lytjs/reactivity';
50
-
51
- const r = ref(0);
52
- const sr = shallowRef({ a: 1 });
53
- const c = computed(() => r.value * 2);
54
-
55
- isShallowRef(r); // false
56
- isShallowRef(sr); // true
57
- isComputedRef(r); // false
58
- isComputedRef(c); // true
59
- ```
60
-
61
- ### computed
62
-
63
- 创建计算属性,自动追踪依赖并缓存结果
64
-
65
- ```typescript
66
- import { computed, ref } from '@lytjs/reactivity';
67
-
68
- const count = ref(1);
69
- const doubled = computed(() => count.value * 2);
70
- console.log(doubled.value); // 2
71
- ```
72
-
73
- ### watch / watchEffect
74
-
75
- 侦听响应式数据变化并执行副作用
76
-
77
- ```typescript
78
- import { watch, watchEffect, ref } from '@lytjs/reactivity';
79
-
80
- const count = ref(0);
81
-
82
- // 侦听特定源
83
- watch(count, (newVal, oldVal) => {
84
- console.log(`count changed from ${oldVal} to ${newVal}`);
85
- });
86
-
87
- // 自动追踪依赖
88
- watchEffect(() => {
89
- console.log(`count is ${count.value}`);
90
- });
91
- ```
92
-
93
- ### effect
94
-
95
- 创建自定义响应式副作用
96
-
97
- ```typescript
98
- import { effect, stop } from '@lytjs/reactivity';
99
-
100
- const runner = effect(() => {
101
- // 副作用逻辑
102
- });
103
-
104
- // 停止副作用
105
- stop(runner);
106
- ```
107
-
108
- ### toRef / toRefs / unref
109
-
110
- 响应式引用工具函数
111
-
112
- ```typescript
113
- import { toRef, toRefs, unref } from '@lytjs/reactivity';
114
-
115
- const state = reactive({ foo: 1, bar: 2 });
116
- const { foo, bar } = toRefs(state);
117
-
118
- const refFoo = toRef(state, 'foo');
119
- const value = unref(refFoo); // 1
120
- ```
121
-
122
- ## Signal API
123
-
124
- Signal 是一种独立的响应式原语,拥有独立的订阅/通知机制,同时桥接 effect 系统保持互操作性。
125
-
126
- ### signal
127
-
128
- 创建可写 Signal
129
-
130
- ```typescript
131
- import { signal, computed } from '@lytjs/reactivity';
132
-
133
- const count = signal(0);
134
-
135
- // 读取值
136
- console.log(count()); // 0
137
-
138
- // 设置值
139
- count.set(1);
140
-
141
- // 通过 updater 更新
142
- count.update((prev) => prev + 1);
143
-
144
- // 停止所有订阅通知,释放资源
145
- count.dispose();
146
- ```
147
-
148
- ### computed (Signal 版本)
149
-
150
- 创建计算 Signal,惰性求值、自动依赖追踪
151
-
152
- ```typescript
153
- import { signal, computed } from '@lytjs/reactivity';
154
-
155
- const count = signal(1);
156
- const doubled = computed(() => count() * 2);
157
-
158
- console.log(doubled()); // 2
159
-
160
- // 停止计算信号的依赖追踪
161
- doubled.dispose();
162
- ```
163
-
164
- ### writableComputedSignal
165
-
166
- 创建可写计算 Signal
167
-
168
- ```typescript
169
- import { signal, writableComputedSignal } from '@lytjs/reactivity';
170
-
171
- const firstName = signal('John');
172
- const lastName = signal('Doe');
173
-
174
- const fullName = writableComputedSignal(
175
- () => `${firstName()} ${lastName()}`,
176
- (val) => {
177
- const [first, last] = val.split(' ');
178
- firstName.set(first);
179
- lastName.set(last);
180
- },
181
- );
182
-
183
- fullName.set('Jane Smith');
184
- ```
185
-
186
- ### signalBatch / signalUntrack
187
-
188
- Signal 批处理和取消追踪
189
-
190
- ```typescript
191
- import { signal, signalBatch, signalUntrack } from '@lytjs/reactivity';
192
-
193
- const a = signal(1);
194
- const b = signal(2);
195
-
196
- // 批量更新:多次 set 只触发一次通知
197
- signalBatch(() => {
198
- a.set(10);
199
- b.set(20);
200
- });
201
-
202
- // 取消追踪:读取 signal 不建立依赖
203
- const value = signalUntrack(() => a());
204
- ```
205
-
206
- ## Effect 批处理
207
-
208
- ### batch / batchAsync
209
-
210
- 批量执行多个响应式更新,减少不必要的重复计算
211
-
212
- ```typescript
213
- import { batch, batchAsync, ref, effect } from '@lytjs/reactivity';
214
-
215
- const a = ref(1);
216
- const b = ref(2);
217
-
218
- // 同步批处理
219
- batch(() => {
220
- a.value = 10;
221
- b.value = 20;
222
- // 在 batch 结束前,effect 不会触发
223
- });
224
-
225
- // 异步批处理
226
- await batchAsync(async () => {
227
- a.value = await fetchValue();
228
- b.value = await fetchOtherValue();
229
- });
230
- ```
231
-
232
- ### batchScope
233
-
234
- 更细粒度的批处理控制
235
-
236
- ```typescript
237
- import { batchScope, batchScopeAsync, batchScopeUntrack, isInBatchScope } from '@lytjs/reactivity';
238
-
239
- // 带选项的批处理
240
- batchScope(
241
- () => {
242
- // 检查是否在批处理作用域内
243
- if (isInBatchScope()) {
244
- console.log('当前在批处理作用域内');
245
- }
246
- },
247
- { flush: 'sync' },
248
- );
249
-
250
- // 异步批处理作用域
251
- await batchScopeAsync(async () => {
252
- // 异步操作
253
- });
254
-
255
- // 取消追踪的批处理
256
- batchScopeUntrack(() => {
257
- // 内部的响应式读取不会建立依赖
258
- });
259
- ```
260
-
261
- ### 嵌套批处理行为
262
-
263
- `batch()` 支持嵌套调用,使用栈追踪状态
264
-
265
- ```typescript
266
- import { batch, effect } from '@lytjs/reactivity';
267
-
268
- batch(() => {
269
- // 追踪已暂停
270
- batch(() => {
271
- // 追踪仍处于暂停状态
272
- });
273
- // 内层 batch 结束后,追踪仍处于暂停状态(由外层 batch 控制)
274
- });
275
- // 外层 batch 结束后,追踪恢复正常
276
- ```
277
-
278
- ## 边界行为与已知限制
279
-
280
- ### `signal()` 的 undefined 参数歧义
281
-
282
- `signal()` 通过 `arguments.length` 来区分读取操作和写入操作。当传入 `undefined` 作为参数时,由于 `arguments.length > 0` 为 `true`,它会被视为**写入操作**。
283
-
284
- ```typescript
285
- import { signal } from '@lytjs/reactivity';
286
-
287
- const sig = signal<number | undefined>(42);
288
- sig(undefined); // 写入操作:将值设为 undefined
289
- sig(); // 读取操作:返回 undefined
290
- ```
291
-
292
- ### `computedSignal.dispose()` 方法
293
-
294
- 调用 `dispose()` 后:
295
-
296
- - 计算信号不再自动追踪依赖变化
297
- - 后续访问将返回最后一次缓存的值
298
- - 依赖该计算信号的其他 effect 不会再因依赖变化而被触发
299
-
300
- ## DevTools 集成
301
-
302
- 在开发环境下,响应式系统会自动初始化 DevTools 全局对象:
303
-
304
- ```typescript
305
- // 浏览器控制台
306
- window.__LYTJS_DEVTOOLS__.getSignals();
307
- window.__LYTJS_DEVTOOLS__.getEffects();
308
- window.__LYTJS_DEVTOOLS__.onSignalChange((id, value) => {
309
- console.log('Signal changed:', id, value);
310
- });
311
- ```
312
-
313
- ## 子路径入口
314
-
315
- ```typescript
316
- // Effect Scope
317
- import { effectScope, getCurrentScope, onScopeDispose } from '@lytjs/reactivity/scope';
318
-
319
- // 异步计算
320
- import { asyncComputed, useAsyncState } from '@lytjs/reactivity/async';
321
- ```
322
-
323
- ## 相关包
324
-
325
- - [@lytjs/core](../core) - 框架核心入口,整合所有子包
326
- - [@lytjs/component](../component) - 组件系统,依赖响应式系统
327
- - [@lytjs/common](../common) - 公共工具库
1
+ # @lytjs/reactivity
2
+
3
+ > LytJS 响应式系统,提供 ref、reactive、computed、watch、Signal 等核心响应式原语
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install @lytjs/reactivity
9
+ ```
10
+
11
+ ## 核心 API
12
+
13
+ ### reactive / shallowReactive / readonly / shallowReadonly
14
+
15
+ 创建响应式对象,支持深层/浅层响应和只读模式
16
+
17
+ ```typescript
18
+ import { reactive, shallowReactive, readonly, shallowReadonly } from '@lytjs/reactivity';
19
+
20
+ const state = reactive({ count: 0 });
21
+ state.count++; // 自动追踪
22
+
23
+ const shallow = shallowReactive({ nested: { value: 1 } });
24
+ shallow.nested.value = 2; // 不会触发更新(浅层响应)
25
+ ```
26
+
27
+ ### ref / shallowRef
28
+
29
+ 创建响应式引用,适用于基本类型值
30
+
31
+ ```typescript
32
+ import { ref, shallowRef, isShallowRef } from '@lytjs/reactivity';
33
+
34
+ const count = ref(0);
35
+ count.value++;
36
+
37
+ const shallow = shallowRef({ nested: { value: 1 } });
38
+ // isShallowRef 类型守卫
39
+ if (isShallowRef(shallow)) {
40
+ console.log('这是一个 ShallowRef');
41
+ }
42
+ ```
43
+
44
+ ### isShallowRef / isComputedRef
45
+
46
+ 类型守卫函数,用于在运行时判断 Ref 类型
47
+
48
+ ```typescript
49
+ import { ref, shallowRef, computed, isShallowRef, isComputedRef } from '@lytjs/reactivity';
50
+
51
+ const r = ref(0);
52
+ const sr = shallowRef({ a: 1 });
53
+ const c = computed(() => r.value * 2);
54
+
55
+ isShallowRef(r); // false
56
+ isShallowRef(sr); // true
57
+ isComputedRef(r); // false
58
+ isComputedRef(c); // true
59
+ ```
60
+
61
+ ### computed
62
+
63
+ 创建计算属性,自动追踪依赖并缓存结果
64
+
65
+ ```typescript
66
+ import { computed, ref } from '@lytjs/reactivity';
67
+
68
+ const count = ref(1);
69
+ const doubled = computed(() => count.value * 2);
70
+ console.log(doubled.value); // 2
71
+ ```
72
+
73
+ ### watch / watchEffect
74
+
75
+ 侦听响应式数据变化并执行副作用
76
+
77
+ ```typescript
78
+ import { watch, watchEffect, ref } from '@lytjs/reactivity';
79
+
80
+ const count = ref(0);
81
+
82
+ // 侦听特定源
83
+ watch(count, (newVal, oldVal) => {
84
+ console.log(`count changed from ${oldVal} to ${newVal}`);
85
+ });
86
+
87
+ // 自动追踪依赖
88
+ watchEffect(() => {
89
+ console.log(`count is ${count.value}`);
90
+ });
91
+ ```
92
+
93
+ ### effect
94
+
95
+ 创建自定义响应式副作用
96
+
97
+ ```typescript
98
+ import { effect, stop } from '@lytjs/reactivity';
99
+
100
+ const runner = effect(() => {
101
+ // 副作用逻辑
102
+ });
103
+
104
+ // 停止副作用
105
+ stop(runner);
106
+ ```
107
+
108
+ ### toRef / toRefs / unref
109
+
110
+ 响应式引用工具函数
111
+
112
+ ```typescript
113
+ import { toRef, toRefs, unref } from '@lytjs/reactivity';
114
+
115
+ const state = reactive({ foo: 1, bar: 2 });
116
+ const { foo, bar } = toRefs(state);
117
+
118
+ const refFoo = toRef(state, 'foo');
119
+ const value = unref(refFoo); // 1
120
+ ```
121
+
122
+ ## Signal API
123
+
124
+ Signal 是一种独立的响应式原语,拥有独立的订阅/通知机制,同时桥接 effect 系统保持互操作性。
125
+
126
+ ### signal
127
+
128
+ 创建可写 Signal
129
+
130
+ ```typescript
131
+ import { signal, computed } from '@lytjs/reactivity';
132
+
133
+ const count = signal(0);
134
+
135
+ // 读取值
136
+ console.log(count()); // 0
137
+
138
+ // 设置值
139
+ count.set(1);
140
+
141
+ // 通过 updater 更新
142
+ count.update((prev) => prev + 1);
143
+
144
+ // 停止所有订阅通知,释放资源
145
+ count.dispose();
146
+ ```
147
+
148
+ ### computed (Signal 版本)
149
+
150
+ 创建计算 Signal,惰性求值、自动依赖追踪
151
+
152
+ ```typescript
153
+ import { signal, computed } from '@lytjs/reactivity';
154
+
155
+ const count = signal(1);
156
+ const doubled = computed(() => count() * 2);
157
+
158
+ console.log(doubled()); // 2
159
+
160
+ // 停止计算信号的依赖追踪
161
+ doubled.dispose();
162
+ ```
163
+
164
+ ### writableComputedSignal
165
+
166
+ 创建可写计算 Signal
167
+
168
+ ```typescript
169
+ import { signal, writableComputedSignal } from '@lytjs/reactivity';
170
+
171
+ const firstName = signal('John');
172
+ const lastName = signal('Doe');
173
+
174
+ const fullName = writableComputedSignal(
175
+ () => `${firstName()} ${lastName()}`,
176
+ (val) => {
177
+ const [first, last] = val.split(' ');
178
+ firstName.set(first);
179
+ lastName.set(last);
180
+ },
181
+ );
182
+
183
+ fullName.set('Jane Smith');
184
+ ```
185
+
186
+ ### signalBatch / signalUntrack
187
+
188
+ Signal 批处理和取消追踪
189
+
190
+ ```typescript
191
+ import { signal, signalBatch, signalUntrack } from '@lytjs/reactivity';
192
+
193
+ const a = signal(1);
194
+ const b = signal(2);
195
+
196
+ // 批量更新:多次 set 只触发一次通知
197
+ signalBatch(() => {
198
+ a.set(10);
199
+ b.set(20);
200
+ });
201
+
202
+ // 取消追踪:读取 signal 不建立依赖
203
+ const value = signalUntrack(() => a());
204
+ ```
205
+
206
+ ## Effect 批处理
207
+
208
+ ### batch / batchAsync
209
+
210
+ 批量执行多个响应式更新,减少不必要的重复计算
211
+
212
+ ```typescript
213
+ import { batch, batchAsync, ref, effect } from '@lytjs/reactivity';
214
+
215
+ const a = ref(1);
216
+ const b = ref(2);
217
+
218
+ // 同步批处理
219
+ batch(() => {
220
+ a.value = 10;
221
+ b.value = 20;
222
+ // 在 batch 结束前,effect 不会触发
223
+ });
224
+
225
+ // 异步批处理
226
+ await batchAsync(async () => {
227
+ a.value = await fetchValue();
228
+ b.value = await fetchOtherValue();
229
+ });
230
+ ```
231
+
232
+ ### batchScope
233
+
234
+ 更细粒度的批处理控制
235
+
236
+ ```typescript
237
+ import { batchScope, batchScopeAsync, batchScopeUntrack, isInBatchScope } from '@lytjs/reactivity';
238
+
239
+ // 带选项的批处理
240
+ batchScope(
241
+ () => {
242
+ // 检查是否在批处理作用域内
243
+ if (isInBatchScope()) {
244
+ console.log('当前在批处理作用域内');
245
+ }
246
+ },
247
+ { flush: 'sync' },
248
+ );
249
+
250
+ // 异步批处理作用域
251
+ await batchScopeAsync(async () => {
252
+ // 异步操作
253
+ });
254
+
255
+ // 取消追踪的批处理
256
+ batchScopeUntrack(() => {
257
+ // 内部的响应式读取不会建立依赖
258
+ });
259
+ ```
260
+
261
+ ### 嵌套批处理行为
262
+
263
+ `batch()` 支持嵌套调用,使用栈追踪状态
264
+
265
+ ```typescript
266
+ import { batch, effect } from '@lytjs/reactivity';
267
+
268
+ batch(() => {
269
+ // 追踪已暂停
270
+ batch(() => {
271
+ // 追踪仍处于暂停状态
272
+ });
273
+ // 内层 batch 结束后,追踪仍处于暂停状态(由外层 batch 控制)
274
+ });
275
+ // 外层 batch 结束后,追踪恢复正常
276
+ ```
277
+
278
+ ## 边界行为与已知限制
279
+
280
+ ### `signal()` 的 undefined 参数歧义
281
+
282
+ `signal()` 通过 `arguments.length` 来区分读取操作和写入操作。当传入 `undefined` 作为参数时,由于 `arguments.length > 0` 为 `true`,它会被视为**写入操作**。
283
+
284
+ ```typescript
285
+ import { signal } from '@lytjs/reactivity';
286
+
287
+ const sig = signal<number | undefined>(42);
288
+ sig(undefined); // 写入操作:将值设为 undefined
289
+ sig(); // 读取操作:返回 undefined
290
+ ```
291
+
292
+ ### `computedSignal.dispose()` 方法
293
+
294
+ 调用 `dispose()` 后:
295
+
296
+ - 计算信号不再自动追踪依赖变化
297
+ - 后续访问将返回最后一次缓存的值
298
+ - 依赖该计算信号的其他 effect 不会再因依赖变化而被触发
299
+
300
+ ## DevTools 集成
301
+
302
+ 在开发环境下,响应式系统会自动初始化 DevTools 全局对象:
303
+
304
+ ```typescript
305
+ // 浏览器控制台
306
+ window.__LYTJS_DEVTOOLS__.getSignals();
307
+ window.__LYTJS_DEVTOOLS__.getEffects();
308
+ window.__LYTJS_DEVTOOLS__.onSignalChange((id, value) => {
309
+ console.log('Signal changed:', id, value);
310
+ });
311
+ ```
312
+
313
+ ## 子路径入口
314
+
315
+ ```typescript
316
+ // Effect Scope
317
+ import { effectScope, getCurrentScope, onScopeDispose } from '@lytjs/reactivity/scope';
318
+
319
+ // 异步计算
320
+ import { asyncComputed, useAsyncState } from '@lytjs/reactivity/async';
321
+ ```
322
+
323
+ ## 相关包
324
+
325
+ - [@lytjs/core](../core) - 框架核心入口,整合所有子包
326
+ - [@lytjs/component](../component) - 组件系统,依赖响应式系统
327
+ - [@lytjs/common](../common) - 公共工具库