@luys/cc-store 1.0.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/.cc-store.md +50 -0
- package/assets/Store.ts +537 -0
- package/assets/Store.ts.meta +9 -0
- package/assets.meta +9 -0
- package/index.ts +135 -0
- package/index.ts.meta +9 -0
- package/package.json +20 -0
- package/package.json.meta +11 -0
package/.cc-store.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# 状态管理模块
|
|
2
|
+
|
|
3
|
+
## 前置条件
|
|
4
|
+
[游戏引擎](https://www.cocos.com/en/creator-download)
|
|
5
|
+
[开发框架](https://github.com/a1076559139/cocos-creator-frame-3d)
|
|
6
|
+
|
|
7
|
+
## 使用案例
|
|
8
|
+
```TS
|
|
9
|
+
import {createStore, bindStore, stopBind, watchStore, stopWatch} from 'db://pkg/@gamex/cc-store';
|
|
10
|
+
|
|
11
|
+
// store.game
|
|
12
|
+
export default class Game implements IStore<Game> {
|
|
13
|
+
// 将当前实例转成Store
|
|
14
|
+
constructor() {
|
|
15
|
+
return createStore(this);// return不可忽略
|
|
16
|
+
}
|
|
17
|
+
// 状态
|
|
18
|
+
count = 0;
|
|
19
|
+
// 更新状态
|
|
20
|
+
setCount(count: number) {
|
|
21
|
+
this.count = count;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// PageHome.ts
|
|
26
|
+
onCountChange(){
|
|
27
|
+
this.label2.string = `count: ${app.store.game.count}`;
|
|
28
|
+
}
|
|
29
|
+
onLoad() {
|
|
30
|
+
// 监听状态变化
|
|
31
|
+
watchStore(this.onCountChange, this);
|
|
32
|
+
// 状态与组件属性绑定
|
|
33
|
+
bindStore(this.label, 'string', () => {
|
|
34
|
+
return app.store.game.count.toString();
|
|
35
|
+
});
|
|
36
|
+
// 状态与节点属性绑定
|
|
37
|
+
bindStore(this.label.node, 'active', () => {
|
|
38
|
+
return app.store.game.count % 2 == 0;
|
|
39
|
+
});
|
|
40
|
+
app.store.game.setCount(10);
|
|
41
|
+
}
|
|
42
|
+
onDestroy() {
|
|
43
|
+
// 在节点或组件被销毁后会自动解绑,一般不需要手动解绑
|
|
44
|
+
// 但如果是通过node.removeFromParent方法移除node,这个时候是不会自动解绑的,如果需要请手动解绑
|
|
45
|
+
stopBind(this.label, 'string');
|
|
46
|
+
stopBind(this.label.node, 'active');
|
|
47
|
+
// watchStore必须要手动停止
|
|
48
|
+
stopWatch(this.onCountChange, this);
|
|
49
|
+
}
|
|
50
|
+
```
|
package/assets/Store.ts
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-this-alias */
|
|
2
|
+
|
|
3
|
+
const lazilyWatchers = new Set<Watcher>();
|
|
4
|
+
export function reactive() {
|
|
5
|
+
lazilyWatchers.forEach(watcher => watcher.run());
|
|
6
|
+
lazilyWatchers.clear();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// 原始对象 => 每个key对应的Watcher实例集合
|
|
10
|
+
const depsMap = new WeakMap<object, Map<PropertyKey, Set<Watcher>>>();
|
|
11
|
+
|
|
12
|
+
// 原始对象 => 父级引用
|
|
13
|
+
const parentMap = new WeakMap<object, { obj: object; prop: PropertyKey }>();
|
|
14
|
+
|
|
15
|
+
// 原始对象 => 代理对象
|
|
16
|
+
const proxyMap = new WeakMap<object, object>();
|
|
17
|
+
|
|
18
|
+
// 当前正在执行的 watcher 实例
|
|
19
|
+
let activeWatcher: Watcher | null = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 收集依赖(只要是访问某个对象的某个属性,都要调用track函数,以收集依赖到当前的watcher中)
|
|
23
|
+
* - 将当前watcher实例添加到对象属性对应的依赖集合中,同时将集合添加到当前watcher的依赖集合中(这一步主要用于在watcher中可以取消依赖追踪,即watcher.cancel())
|
|
24
|
+
*/
|
|
25
|
+
function track(obj: object, prop: PropertyKey) {
|
|
26
|
+
if (!activeWatcher) return;
|
|
27
|
+
|
|
28
|
+
let objDeps = depsMap.get(obj);
|
|
29
|
+
if (!objDeps) {
|
|
30
|
+
objDeps = new Map();
|
|
31
|
+
depsMap.set(obj, objDeps);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 每个key对应的Watcher实例集合
|
|
35
|
+
let propDeps = objDeps.get(prop);
|
|
36
|
+
if (!propDeps) {
|
|
37
|
+
propDeps = new Set();
|
|
38
|
+
objDeps.set(prop, propDeps);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!propDeps.has(activeWatcher)) {
|
|
42
|
+
propDeps.add(activeWatcher);
|
|
43
|
+
activeWatcher.deps.add(propDeps);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 触发依赖追踪
|
|
49
|
+
* @param obj
|
|
50
|
+
* @param prop
|
|
51
|
+
*/
|
|
52
|
+
function trigger(obj: object, prop: PropertyKey) {
|
|
53
|
+
const objDeps = depsMap.get(obj);
|
|
54
|
+
if (!objDeps) return;
|
|
55
|
+
|
|
56
|
+
const propDeps = objDeps.get(prop);
|
|
57
|
+
if (!propDeps) return;
|
|
58
|
+
|
|
59
|
+
// 延迟执行
|
|
60
|
+
propDeps.forEach(watcher => {
|
|
61
|
+
lazilyWatchers.add(watcher);
|
|
62
|
+
});
|
|
63
|
+
// 立即执行(创建副本避免循环)
|
|
64
|
+
// new Set(propDeps).forEach(watcher => watcher.run());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 触发依赖追踪的后续操作(向上触发父级依赖追踪)
|
|
69
|
+
* @param obj
|
|
70
|
+
*/
|
|
71
|
+
function triggerOver(obj: object) {
|
|
72
|
+
const parent = parentMap.get(obj);
|
|
73
|
+
if (!parent) return;
|
|
74
|
+
|
|
75
|
+
const grandpa = parentMap.get(parent.obj);
|
|
76
|
+
if (!grandpa) return;
|
|
77
|
+
|
|
78
|
+
const objDeps = depsMap.get(grandpa.obj);
|
|
79
|
+
if (!objDeps) return;
|
|
80
|
+
const propDeps = objDeps.get(grandpa.prop);
|
|
81
|
+
if (!propDeps) return;
|
|
82
|
+
|
|
83
|
+
// 延迟执行
|
|
84
|
+
propDeps.forEach(watcher => {
|
|
85
|
+
lazilyWatchers.add(watcher);
|
|
86
|
+
});
|
|
87
|
+
// 立即执行(创建副本避免循环)
|
|
88
|
+
// new Set(propDeps).forEach(watcher => watcher.run());
|
|
89
|
+
|
|
90
|
+
triggerOver(parent.obj);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
class Watcher {
|
|
94
|
+
deps: Set<Set<Watcher>>;
|
|
95
|
+
|
|
96
|
+
constructor(public callback: () => void, public target?: unknown) {
|
|
97
|
+
this.deps = new Set();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
run() {
|
|
101
|
+
// 清理旧依赖
|
|
102
|
+
this.deps.forEach(dep => dep.delete(this));
|
|
103
|
+
this.deps.clear();
|
|
104
|
+
|
|
105
|
+
// 收集新依赖
|
|
106
|
+
const prevWatcher = activeWatcher;
|
|
107
|
+
activeWatcher = this;
|
|
108
|
+
this.callback.call(this.target);
|
|
109
|
+
activeWatcher = prevWatcher;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
cancel() {
|
|
113
|
+
this.deps.forEach(dep => dep.delete(this));
|
|
114
|
+
this.deps.clear();
|
|
115
|
+
|
|
116
|
+
if (lazilyWatchers.has(this)) {
|
|
117
|
+
lazilyWatchers.delete(this);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function watch(callback: () => void, target?: unknown): () => void {
|
|
123
|
+
const watcher = new Watcher(callback, target);
|
|
124
|
+
watcher.run();
|
|
125
|
+
return () => watcher.cancel();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 数组方法
|
|
129
|
+
function handleArray(obj: Array<any>, prop: PropertyKey, receiver: any) {
|
|
130
|
+
// 修改类操作
|
|
131
|
+
if (prop === 'splice') {
|
|
132
|
+
return function (...args: any[]) {
|
|
133
|
+
const start = args[0] as number;
|
|
134
|
+
const deleteCount = args.length > 1 ? args[1] : obj.length - start;
|
|
135
|
+
const addCount = args.length > 2 ? (args.length - 2) : 0;
|
|
136
|
+
|
|
137
|
+
const result = Array.prototype.splice.apply(obj, args);
|
|
138
|
+
|
|
139
|
+
let triggerCalled = false;
|
|
140
|
+
if (deleteCount === addCount) {
|
|
141
|
+
for (let i = start; i < start + deleteCount; ++i) {
|
|
142
|
+
triggerCalled = true;
|
|
143
|
+
trigger(obj, i);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
for (let i = start; i < obj.length; ++i) {
|
|
147
|
+
trigger(obj, i);
|
|
148
|
+
}
|
|
149
|
+
trigger(obj, 'length');
|
|
150
|
+
triggerCalled = true;
|
|
151
|
+
}
|
|
152
|
+
if (triggerCalled) {
|
|
153
|
+
trigger(obj, Symbol.iterator);
|
|
154
|
+
triggerOver(obj);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (prop === 'fill') {
|
|
161
|
+
return function (...args: any[]) {
|
|
162
|
+
const oldArr = obj.slice();
|
|
163
|
+
const result = Array.prototype.fill.apply(obj, args);
|
|
164
|
+
|
|
165
|
+
const length = Math.max(oldArr.length, obj.length);
|
|
166
|
+
|
|
167
|
+
let triggerCalled = false;
|
|
168
|
+
for (let i = 0; i < length; ++i) {
|
|
169
|
+
if (oldArr[i] === obj[i]) continue;
|
|
170
|
+
triggerCalled = true;
|
|
171
|
+
trigger(obj, i);
|
|
172
|
+
}
|
|
173
|
+
if (oldArr.length !== obj.length) {
|
|
174
|
+
trigger(obj, 'length');
|
|
175
|
+
triggerCalled = true;
|
|
176
|
+
}
|
|
177
|
+
if (triggerCalled) {
|
|
178
|
+
trigger(obj, Symbol.iterator);
|
|
179
|
+
triggerOver(obj);
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (prop === 'push') {
|
|
185
|
+
return function (...args: any[]) {
|
|
186
|
+
const oldLen = obj.length;
|
|
187
|
+
const result = Array.prototype.push.apply(obj, args);
|
|
188
|
+
if (oldLen === obj.length) return result;
|
|
189
|
+
|
|
190
|
+
for (let i = oldLen; i < obj.length; ++i) {
|
|
191
|
+
trigger(obj, i);
|
|
192
|
+
}
|
|
193
|
+
trigger(obj, 'length');
|
|
194
|
+
trigger(obj, Symbol.iterator);
|
|
195
|
+
triggerOver(obj);
|
|
196
|
+
return result;
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (prop === 'pop') {
|
|
200
|
+
return function () {
|
|
201
|
+
const oldLen = obj.length;
|
|
202
|
+
const result = Array.prototype.pop.call(obj);
|
|
203
|
+
if (oldLen === obj.length) return result;
|
|
204
|
+
|
|
205
|
+
trigger(obj, obj.length);
|
|
206
|
+
trigger(obj, 'length');
|
|
207
|
+
trigger(obj, Symbol.iterator);
|
|
208
|
+
triggerOver(obj);
|
|
209
|
+
return result;
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (prop === 'shift' || prop === 'unshift') {
|
|
213
|
+
return function (...args: any[]) {
|
|
214
|
+
const oldLen = obj.length;
|
|
215
|
+
const result = Array.prototype[prop].apply(obj, args);
|
|
216
|
+
if (oldLen === obj.length) return result;
|
|
217
|
+
|
|
218
|
+
const length = Math.max(oldLen, obj.length);
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < length; ++i) {
|
|
221
|
+
trigger(obj, i);
|
|
222
|
+
}
|
|
223
|
+
trigger(obj, 'length');
|
|
224
|
+
trigger(obj, Symbol.iterator);
|
|
225
|
+
triggerOver(obj);
|
|
226
|
+
return result;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (prop === 'sort' || prop === 'reverse') {
|
|
230
|
+
return function (...args: any[]) {
|
|
231
|
+
const result = Array.prototype[prop].apply(obj, args);
|
|
232
|
+
if (obj.length <= 1) return result;
|
|
233
|
+
|
|
234
|
+
for (let i = 0; i < obj.length; ++i) {
|
|
235
|
+
trigger(obj, i);
|
|
236
|
+
}
|
|
237
|
+
trigger(obj, Symbol.iterator);
|
|
238
|
+
triggerOver(obj);
|
|
239
|
+
|
|
240
|
+
return result;
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
// 获取类操作
|
|
244
|
+
if (prop === 'forEach') {
|
|
245
|
+
return function (callback: any) {
|
|
246
|
+
track(obj, 'size');
|
|
247
|
+
obj.forEach((val, key) => {
|
|
248
|
+
track(obj, key);
|
|
249
|
+
callback(create(val), key, obj);
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
255
|
+
return create(value);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function handleMap(obj: Map<any, any>, prop: PropertyKey, receiver: any) {
|
|
259
|
+
// 修改类操作
|
|
260
|
+
if (prop === 'set') {
|
|
261
|
+
return function (key: any, val: any) {
|
|
262
|
+
const oldVal = Map.prototype.get.call(obj, key);
|
|
263
|
+
if (oldVal === val) return this;
|
|
264
|
+
|
|
265
|
+
const hasProp = Map.prototype.has.call(obj, key);
|
|
266
|
+
Map.prototype.set.call(obj, key, val);
|
|
267
|
+
|
|
268
|
+
// 触发更新
|
|
269
|
+
trigger(obj, key);
|
|
270
|
+
if (!hasProp) {
|
|
271
|
+
trigger(obj, 'size');
|
|
272
|
+
}
|
|
273
|
+
trigger(obj, Symbol.iterator);
|
|
274
|
+
triggerOver(obj);
|
|
275
|
+
|
|
276
|
+
return this;
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (prop === 'delete') {
|
|
280
|
+
return function (key: any) {
|
|
281
|
+
const hasProp = Map.prototype.has.call(obj, key);
|
|
282
|
+
if (!hasProp) return true;
|
|
283
|
+
|
|
284
|
+
if (Map.prototype.delete.call(obj, key)) {
|
|
285
|
+
// 触发更新
|
|
286
|
+
trigger(obj, key);
|
|
287
|
+
trigger(obj, 'size');
|
|
288
|
+
trigger(obj, Symbol.iterator);
|
|
289
|
+
triggerOver(obj);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return false;
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (prop === 'clear') {
|
|
297
|
+
return function () {
|
|
298
|
+
if (obj.size === 0) return;
|
|
299
|
+
|
|
300
|
+
obj.forEach((val: any, key: any) => {
|
|
301
|
+
if (!Map.prototype.delete.call(obj, key)) return;
|
|
302
|
+
// 触发更新
|
|
303
|
+
trigger(obj, key);
|
|
304
|
+
});
|
|
305
|
+
trigger(obj, 'size');
|
|
306
|
+
trigger(obj, Symbol.iterator);
|
|
307
|
+
triggerOver(obj);
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
// 获取类操作
|
|
311
|
+
if (prop === 'has') {
|
|
312
|
+
return function (key: any) {
|
|
313
|
+
track(obj, key);
|
|
314
|
+
return Map.prototype.has.call(obj, key);
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
if (prop === 'get') {
|
|
318
|
+
return function (key: any) {
|
|
319
|
+
track(obj, key);
|
|
320
|
+
const val = Map.prototype.get.call(obj, key);
|
|
321
|
+
return create(val);
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (prop === 'forEach') {
|
|
325
|
+
return function (callback: any) {
|
|
326
|
+
track(obj, 'size');
|
|
327
|
+
obj.forEach((val, key) => {
|
|
328
|
+
track(obj, key);
|
|
329
|
+
callback(create(val), key, obj);
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (prop === 'keys') {
|
|
334
|
+
return function () {
|
|
335
|
+
track(obj, 'size');
|
|
336
|
+
return Map.prototype.keys.call(obj);
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (prop === 'values' || prop === 'entries') {
|
|
340
|
+
return function () {
|
|
341
|
+
track(obj, 'size');
|
|
342
|
+
return () => { // 返回迭代器工厂函数
|
|
343
|
+
return new Proxy(Map.prototype[prop].call(obj), {
|
|
344
|
+
get: (iterator, method) => {
|
|
345
|
+
if (method === 'next') {
|
|
346
|
+
return () => {
|
|
347
|
+
const value = iterator.next();
|
|
348
|
+
return create(value);
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return Reflect.get(iterator, method, receiver);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const value = Reflect.get(obj, prop);
|
|
359
|
+
return create(value);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function handleSet(obj: Set<any>, prop: PropertyKey, receiver: any) {
|
|
363
|
+
// 修改类操作
|
|
364
|
+
if (prop === 'add' || prop === 'delete' || prop === 'clear') {
|
|
365
|
+
return function (val: any) {
|
|
366
|
+
const oldSize = obj.size;
|
|
367
|
+
const result = Set.prototype[prop].call(obj, val);
|
|
368
|
+
|
|
369
|
+
// 触发更新
|
|
370
|
+
if (oldSize !== obj.size) {
|
|
371
|
+
trigger(obj, 'size');
|
|
372
|
+
trigger(obj, Symbol.iterator);
|
|
373
|
+
triggerOver(obj);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// add方法返回的是Set对象,但是这里要转换为返回Proxy对象,以便后续可以继续拦截操作
|
|
377
|
+
if (result === obj) {
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
// 获取类操作
|
|
384
|
+
if (prop === 'has') {
|
|
385
|
+
return function (val: any) {
|
|
386
|
+
track(obj, 'size');
|
|
387
|
+
return Set.prototype.has.call(obj, val);
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (prop === 'forEach') {
|
|
391
|
+
return function (callback: any) {
|
|
392
|
+
track(obj, 'size');
|
|
393
|
+
obj.forEach((val, value2) => {
|
|
394
|
+
callback(create(val), value2, obj);
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (prop === 'keys') {
|
|
399
|
+
return function () {
|
|
400
|
+
track(obj, 'size');
|
|
401
|
+
return Set.prototype.keys.call(obj);
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (prop === 'values' || prop === 'entries') {
|
|
405
|
+
return () => { // 返回迭代器工厂函数
|
|
406
|
+
track(obj, 'size');
|
|
407
|
+
return new Proxy(Set.prototype[prop].call(obj), {
|
|
408
|
+
get: (iterator, method) => {
|
|
409
|
+
if (method === 'next') {
|
|
410
|
+
return () => {
|
|
411
|
+
const value = iterator.next();
|
|
412
|
+
return create(value);
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return Reflect.get(iterator, method, receiver);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const value = Reflect.get(obj, prop);
|
|
422
|
+
return create(value);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function create<T extends object>(target: T): T {
|
|
426
|
+
if (target instanceof WeakMap) {
|
|
427
|
+
console.error('Store: 不支持WeakMap类型数据');
|
|
428
|
+
return target;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (target === null || target === undefined || typeof target !== 'object'
|
|
432
|
+
|| target instanceof Date || target instanceof RegExp) {
|
|
433
|
+
return target;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (proxyMap.has(target)) {
|
|
437
|
+
return proxyMap.get(target) as T;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const proxy = new Proxy(target, {
|
|
441
|
+
get(obj, prop, receiver) {
|
|
442
|
+
// 依赖收集
|
|
443
|
+
track(obj, prop);
|
|
444
|
+
|
|
445
|
+
// Symbol.iterator(迭代器,用于for...of循环)
|
|
446
|
+
if (prop === Symbol.iterator) {
|
|
447
|
+
return () => { // 返回迭代器工厂函数
|
|
448
|
+
return new Proxy(obj[Symbol.iterator](), {
|
|
449
|
+
get: (iterator, method) => {
|
|
450
|
+
if (method === 'next') {
|
|
451
|
+
return () => {
|
|
452
|
+
const value = iterator.next();
|
|
453
|
+
return create(value);
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return Reflect.get(iterator, method, receiver);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 访问数组属性
|
|
463
|
+
if (Array.isArray(obj)) {
|
|
464
|
+
return handleArray(obj, prop, receiver);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// 访问Map属性
|
|
468
|
+
if (obj instanceof Map) {
|
|
469
|
+
return handleMap(obj, prop, receiver);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 访问Set属性
|
|
473
|
+
if (obj instanceof Set) {
|
|
474
|
+
return handleSet(obj, prop, receiver);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 获取值
|
|
478
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
479
|
+
|
|
480
|
+
// 如果值是函数,直接返回
|
|
481
|
+
if (typeof value === 'function') {
|
|
482
|
+
return value;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 如果是WeakMap,直接返回
|
|
486
|
+
if (value instanceof WeakMap) {
|
|
487
|
+
console.warn('Store: 不支持追踪WeakMap内的数据');
|
|
488
|
+
return value;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// 一些基本类型,直接返回
|
|
492
|
+
if (value === null || value === undefined || typeof value !== 'object'
|
|
493
|
+
|| value instanceof Date || value instanceof RegExp) {
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 记录对象属性的父级引用
|
|
498
|
+
parentMap.set(value, { obj, prop });
|
|
499
|
+
|
|
500
|
+
// 递归响应式转换
|
|
501
|
+
return create(value);
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
set(obj, prop, value, receiver) {
|
|
505
|
+
const oldVal = Reflect.get(obj, prop, receiver);
|
|
506
|
+
if (oldVal === value) return true;
|
|
507
|
+
|
|
508
|
+
const result = Reflect.set(obj, prop, value, receiver);
|
|
509
|
+
|
|
510
|
+
if (result) {
|
|
511
|
+
// 触发更新
|
|
512
|
+
trigger(obj, prop);
|
|
513
|
+
trigger(obj, Symbol.iterator);
|
|
514
|
+
triggerOver(obj);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return result;
|
|
518
|
+
},
|
|
519
|
+
|
|
520
|
+
deleteProperty(obj, prop) {
|
|
521
|
+
const hasProp = Reflect.has(obj, prop);
|
|
522
|
+
const result = Reflect.deleteProperty(obj, prop);
|
|
523
|
+
|
|
524
|
+
// 触发更新
|
|
525
|
+
if (result && hasProp) {
|
|
526
|
+
trigger(obj, prop);
|
|
527
|
+
trigger(obj, Symbol.iterator);
|
|
528
|
+
triggerOver(obj);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
proxyMap.set(target, proxy);
|
|
536
|
+
return proxy as T;
|
|
537
|
+
}
|
package/assets.meta
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Component, Director, director, Node } from 'cc';
|
|
2
|
+
import { create, reactive, watch } from './assets/Store';
|
|
3
|
+
|
|
4
|
+
director.on(Director.EVENT_AFTER_UPDATE, reactive);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 立即通知监听者数据变化了
|
|
8
|
+
* - watchStore和bindStore监听的数据变化,都是延迟到Director.EVENT_AFTER_UPDATE中触发的,使用此函数可以立即通知变化
|
|
9
|
+
*/
|
|
10
|
+
export function notifyWatchers() {
|
|
11
|
+
reactive();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 在constructor中执行createStore(this),自动将类的实例转换成store
|
|
16
|
+
*/
|
|
17
|
+
export function createStore<T extends object>(target: T): T {
|
|
18
|
+
return create(target);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const WatchCache: Array<[Function, unknown, Function | null]> = [];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 监听数据变化
|
|
25
|
+
* @param watcher 响应式函数
|
|
26
|
+
* @param target 函数绑定的上下文
|
|
27
|
+
* @param once 只监听一次
|
|
28
|
+
*/
|
|
29
|
+
export function watchStore(watcher: () => any, target?: unknown, once?: boolean) {
|
|
30
|
+
if (once) {
|
|
31
|
+
watch(watcher, target)();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const watchItem = [watcher, target, null] as [Function, unknown, Function | null];
|
|
36
|
+
WatchCache.push(watchItem);
|
|
37
|
+
|
|
38
|
+
const disposer = watch(watcher, target);
|
|
39
|
+
if (WatchCache.indexOf(watchItem) !== -1) {
|
|
40
|
+
watchItem[2] = disposer;
|
|
41
|
+
} else {
|
|
42
|
+
disposer();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 取消监听数据变化
|
|
48
|
+
* @description 在不需要时一定要手动调用此方法,否则会造成内存泄漏
|
|
49
|
+
* @param watcher 响应式函数
|
|
50
|
+
* @param target 函数绑定的上下文
|
|
51
|
+
*/
|
|
52
|
+
export function stopWatch(watcher: () => any, target?: unknown) {
|
|
53
|
+
for (let index = 0; index < WatchCache.length; index++) {
|
|
54
|
+
const [_watcher, _target, _disposer] = WatchCache[index];
|
|
55
|
+
if (watcher !== _watcher) continue;
|
|
56
|
+
if (typeof target !== 'undefined' && target !== _target) continue;
|
|
57
|
+
WatchCache.splice(index--, 1);
|
|
58
|
+
if (_disposer) _disposer();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 判断是否正在监听数据变化
|
|
64
|
+
* @param watcher 响应式函数
|
|
65
|
+
* @param target 函数绑定的上下文
|
|
66
|
+
*/
|
|
67
|
+
export function isWatching(watcher: () => any, target?: unknown) {
|
|
68
|
+
for (let index = 0; index < WatchCache.length; index++) {
|
|
69
|
+
const [_watcher, _target] = WatchCache[index];
|
|
70
|
+
if (watcher !== _watcher) continue;
|
|
71
|
+
if (typeof target !== 'undefined' && target !== _target) continue;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const BindCache: Map<Component | Node, Map<any, [Function, Function]>> = new Map();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 将Node或Component的属性与store绑定
|
|
81
|
+
* @description 在Node或Component销毁时将自动解绑
|
|
82
|
+
* @description 但如果是调用node.removeFromParent移除node,并不会自动解绑,如果需要请手动解绑
|
|
83
|
+
*/
|
|
84
|
+
export function bindStore<T extends Component | Node, K extends keyof T>(obj: T, key: K, value: () => T[K]) {
|
|
85
|
+
let map = BindCache.get(obj);
|
|
86
|
+
if (!map) {
|
|
87
|
+
map = new Map();
|
|
88
|
+
BindCache.set(obj, map);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 如果已经绑定过了,则不再重复绑定
|
|
92
|
+
if (map.has(key)) return;
|
|
93
|
+
|
|
94
|
+
const callback = (comOrNode: T) => {
|
|
95
|
+
if (Node.isNode(comOrNode)) {
|
|
96
|
+
return stopBind(obj, key);
|
|
97
|
+
}
|
|
98
|
+
if (comOrNode !== obj) return;
|
|
99
|
+
stopBind(obj, key);
|
|
100
|
+
};
|
|
101
|
+
const disposer = watch(() => obj[key] = value());
|
|
102
|
+
map.set(key, [disposer, callback]);
|
|
103
|
+
|
|
104
|
+
if (Node.isNode(obj)) {
|
|
105
|
+
obj.on(Node.EventType.NODE_DESTROYED, callback);
|
|
106
|
+
} else {
|
|
107
|
+
obj.node.on(Node.EventType.COMPONENT_REMOVED, callback);
|
|
108
|
+
obj.node.on(Node.EventType.NODE_DESTROYED, callback);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 将Node或Component的属性与store解绑
|
|
114
|
+
*/
|
|
115
|
+
export function stopBind<T extends Component | Node, K extends keyof T>(obj: T, key: K) {
|
|
116
|
+
const map = BindCache.get(obj);
|
|
117
|
+
if (!map) return;
|
|
118
|
+
|
|
119
|
+
const fns = map.get(key);
|
|
120
|
+
if (!fns) return;
|
|
121
|
+
|
|
122
|
+
map.delete(key);
|
|
123
|
+
if (map.size === 0) {
|
|
124
|
+
BindCache.delete(obj);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const [disposer, callback] = fns;
|
|
128
|
+
if (Node.isNode(obj)) {
|
|
129
|
+
obj.off(Node.EventType.NODE_DESTROYED, callback);
|
|
130
|
+
} else {
|
|
131
|
+
obj.node.off(Node.EventType.COMPONENT_REMOVED, callback);
|
|
132
|
+
obj.node.off(Node.EventType.NODE_DESTROYED, callback);
|
|
133
|
+
}
|
|
134
|
+
disposer();
|
|
135
|
+
}
|
package/index.ts.meta
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@luys/cc-store",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"engine": ">=3.8.0",
|
|
5
|
+
"description": "状态管理模块",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://gitee.com/cocos2d-zp/cococs-creator-frame-3d"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"registry": "https://registry.npmjs.org"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"readme": ".cc-store.md"
|
|
20
|
+
}
|