-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreactivity.js
100 lines (88 loc) · 2.61 KB
/
reactivity.js
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
const ReactivityErrors = Object.freeze({
RecursiveWatch: 'Recursive updates detected. This is likely caused by a watcher that mutates its own depencies.',
});
const nextTick = () => new Promise((resolve) => {
setTimeout(resolve, 0);
});
const currentDependencies = new Set();
const registeredWatchers = [];
const RECURSION_LIMIT = 100;
setInterval(() => {
registeredWatchers.forEach((w) => { w.callCount = 0; });
}, 0);
const watch = (fn, { async = true } = {}) => {
const watcher = {
callCount: 0,
dependencies: new Set(),
};
const callback = () => {
watcher.callCount += 1;
if (watcher.callCount > RECURSION_LIMIT) {
throw new Error(ReactivityErrors.RecursiveWatch);
}
currentDependencies.clear();
fn();
watcher.dependencies = new Set(currentDependencies);
}
watcher.callback = async
? () => { nextTick().then(callback); }
: callback;
callback();
registeredWatchers.push(watcher);
};
// TODO: automatically unwrap refs
const reactiveProxyHandler = (symbolsForProperties) => {
const getSymbol = (property) => {
if (!symbolsForProperties.has(property)) {
symbolsForProperties.set(property, Symbol(property));
}
return symbolsForProperties.get(property);
};
return {
get(target, property, receiver) {
const symbol = getSymbol(property);
currentDependencies.add(symbol);
const value = Reflect.get(target, property, receiver);
if (value && typeof value === 'object') {
return new Proxy(value, reactiveProxyHandler(symbolsForProperties));
}
return value;
},
set(target, property, value, receiver) {
const symbol = getSymbol(property);
const result = Reflect.set(target, property, value, receiver);
registeredWatchers
.filter(({ dependencies }) => dependencies.has(symbol))
.forEach(({ callback }) => callback());
return result;
},
deleteProperty(target, property) {
const symbol = getSymbol(property);
const result = Reflect.deleteProperty(target, property);
registeredWatchers
.filter(({ dependencies }) => dependencies.has(symbol))
.forEach(({ callback }) => callback());
return result;
},
};
};
const reactive = (value) => {
const symbolsForProperties = new Map();
return new Proxy(value, reactiveProxyHandler(symbolsForProperties));
};
const ref = (value) => reactive({ value });
const computed = (fn) => {
const value = ref(undefined);
watch(() => {
value.value = fn();
}, { async: false });
return value;
};
module.exports = {
reactive,
ref,
watch,
nextTick,
computed,
ReactivityErrors,
};