-
Notifications
You must be signed in to change notification settings - Fork 389
/
observable.ts
134 lines (112 loc) · 3.46 KB
/
observable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import { hasOwnProp, isObject, forEachObject, last } from './common';
import { Dictionary } from '../store/types';
type BooleanSet = Dictionary<boolean>;
interface ObserverInfo {
fn: Function;
targetObserverIdSets: BooleanSet[];
}
export type Observable<T extends Dictionary<any>> = T & {
__storage__: T;
__propObserverIdSetMap__: Dictionary<BooleanSet>;
};
const generateObserverId = (() => {
let lastId = 0;
return () => {
lastId += 1;
return `@observer${lastId}`;
};
})();
// store all observer info
const observerInfoMap: Dictionary<ObserverInfo> = {};
// observerId stack for managing recursive observing calls
const observerIdStack: string[] = [];
function callObserver(observerId: string) {
observerIdStack.push(observerId);
observerInfoMap[observerId].fn();
observerIdStack.pop();
}
function setValue<T, K extends keyof T>(
storage: T,
observerIdSet: BooleanSet,
key: keyof T,
value: T[K]
) {
if (storage[key] !== value) {
storage[key] = value;
Object.keys(observerIdSet).forEach(observerId => {
callObserver(observerId);
});
}
}
export function isObservable<T>(resultObj: T): resultObj is Observable<T> {
return isObject(resultObj) && hasOwnProp(resultObj, '__storage__');
}
export function observe(fn: Function) {
const observerId = generateObserverId();
observerInfoMap[observerId] = { fn, targetObserverIdSets: [] };
callObserver(observerId);
// return unobserve function
return () => {
observerInfoMap[observerId].targetObserverIdSets.forEach(idSet => {
delete idSet[observerId];
});
};
}
export function observable<T extends Dictionary<any>>(obj: T): Observable<T> {
if (isObservable(obj)) {
throw new Error('Target object is already Reactive');
}
if (Array.isArray(obj)) {
throw new Error('Array object cannot be Reactive');
}
const storage = {} as T;
const propObserverIdSetMap = {} as Dictionary<BooleanSet>;
const resultObj = {} as T;
Object.defineProperties(resultObj, {
__storage__: { value: storage },
__propObserverIdSetMap__: { value: propObserverIdSetMap }
});
Object.keys(obj).forEach(key => {
const getter = (Object.getOwnPropertyDescriptor(obj, key) || {}).get;
const observerIdSet: BooleanSet = (propObserverIdSetMap[key] = {});
Object.defineProperty(resultObj, key, {
configurable: true,
get() {
const observerId = last(observerIdStack);
if (observerId && !observerIdSet[observerId]) {
observerIdSet[observerId] = true;
observerInfoMap[observerId].targetObserverIdSets.push(observerIdSet);
}
return storage[key];
}
});
if (typeof getter === 'function') {
observe(() => {
const value = getter.call(resultObj);
setValue(storage, observerIdSet, key, value);
});
} else {
storage[key] = obj[key];
Object.defineProperty(resultObj, key, {
set(value) {
setValue(storage, observerIdSet, key, value);
}
});
}
});
return resultObj as Observable<T>;
}
export function notify<T, K extends keyof T>(obj: T, key: K) {
if (isObservable(obj)) {
Object.keys(obj.__propObserverIdSetMap__[key as string]).forEach(observerId => {
callObserver(observerId);
});
}
}
export function getOriginObject<T>(obj: Observable<T>) {
const result = {} as T;
forEachObject((value, key) => {
result[key] = isObservable(value) ? getOriginObject(value) : value;
}, obj.__storage__);
return result;
}