-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrefactor.js
146 lines (127 loc) · 3.46 KB
/
refactor.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
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
135
136
137
138
139
140
141
142
143
144
145
146
'use strict'
const createContext = []
const recomputeContext = []
const queuedUpdates = {}
let finishedRecomputes = {}
function isNode(el) {
return el && el.nodeName && el.nodeType
}
function peek(arr) {
return arr[arr.length - 1]
}
function shouldUpdate(prev, next) {
return prev !== next
}
const r = function (init, name) {
const place = typeof name === 'string' ? new Error(name || 'Reactive') : new Error('Reactive')
const id = typeof name === 'symbol' ? name : Symbol()
let proxy
let cachedValue
let ctx
const unbox = () => (cachedValue || init)
const recompute = typeof init === 'function' ? init : unbox
let domUpdateHandled = false
const recomputeThis = () => {
domUpdateHandled = true
return cachedValue
}
recomputeThis.toString = init.toString
recomputeThis.cachedValue = cachedValue
function onChange() {
const prevValue = cachedValue
// domUpdateHandled = false
recomputeThis.cachedValue = cachedValue
cachedValue = recompute.call(recomputeThis)
// cachedValue = nextValue
recomputeThis.cachedValue = cachedValue
// if (domUpdateHandled) {
// return
// }
// if (isNode(prevValue)) {
// // Assume nextValue is also a node or null.
// if (!nextValue) {
// prevValue.remove()
// } else {
// prevValue.replaceWith(nextValue)
// }
// return
// }
if (shouldUpdate(prevValue, cachedValue)) {
updateDependents()
}
}
ctx = {id, onChange}
const dependents = {}
function register() {
const ctx = peek(createContext)
if (ctx) {
// This overrides the previous reference which allows garbage collection.
dependents[ctx.id] = ctx
}
}
function updateDependents() {
if (!queuedUpdates[id]) { // queue self to batch updates to dependents
queuedUpdates[id] = true
queueMicrotask(() => {
delete queuedUpdates[id]
for (const ctx of Object.values(dependents)) {
if (finishedRecomputes[ctx.id]) {
// A series of microtasks is prevented from updating the
// same context twice.
console.error('Loop', place)
return
}
ctx.onChange() // this might queue another update microtask
finishedRecomputes[ctx.id] = true
}
if (Object.keys(queuedUpdates).length) {
// another microtask is queued after this one,
// defer cleanup to that one.
return
}
// Reset loop detection before next task.
// This happens after all microtasks have ran.
finishedRecomputes = {}
})
}
}
proxy = new Proxy(unbox, {
get: (unbox, prop) => {
register()
if (prop === 'toString') {
return () => init.toString()
} else if (prop === 'cachedValue') {
return cachedValue
}
},
apply: (target, thisArg, args) => {
if (!args.length) {
register()
return cachedValue
} else {
cachedValue = args[0]
recomputeThis.cachedValue = cachedValue
updateDependents()
}
}
})
createContext.push(ctx)
recomputeThis.isInit = true
cachedValue = recompute.call(recomputeThis)
recomputeThis.isInit = false
recomputeThis.cachedValue = cachedValue
createContext.pop()
return proxy
}
const a = r(0)
r(function () {
console.log('this())', this(), this.cachedValue)
if (this()) {
return this() + 1
} else {
return a()
}
}, a[Symbol.for('__')].id)
a(1)
a(0)
a(0)