-
Notifications
You must be signed in to change notification settings - Fork 2
/
with-roots.ts
108 lines (85 loc) · 2.12 KB
/
with-roots.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
// A signal implementation that allows for cleanup functions.
export type VoidFunction = (this: void) => void
export type VoidFunctionSet = Set<(this: void) => void>
function warn(message: string) {
console.warn(message)
}
function safeCall(fn: VoidFunction) {
try {
fn()
} catch {}
}
let currentCleanups: VoidFunctionSet | undefined
let currentEffect: (() => void) | undefined
export class EffectRoot {
static run<T>(fn: () => T): T {
return new EffectRoot().run(fn)
}
#cleanupList: VoidFunctionSet = new Set()
#cleanedUp = false
#myCleanup = this.cleanup.bind(this)
run<T>(this: EffectRoot, fn: () => T): T {
if (this.#cleanedUp) {
throw new Error("Cannot use an effect root that has been cleaned up.")
}
const parentCleanups = currentCleanups
if (parentCleanups) {
parentCleanups.add(this.#myCleanup)
}
try {
currentCleanups = this.#cleanupList
return fn()
} finally {
currentCleanups = parentCleanups
}
}
cleanup(this: EffectRoot) {
this.#cleanedUp = true
this.#cleanupList.forEach(safeCall)
this.#cleanupList.clear()
}
get cleanedUp() {
return this.#cleanedUp
}
}
export function onCleanup(fn: VoidFunction) {
if (currentCleanups) {
currentCleanups.add(fn)
} else {
warn(
"[onCleanup]: Cleanup functions created outside of an effect root will never be run.",
)
}
}
export function createEffect(fn: VoidFunction) {
function wrapper() {
const parentEffect = currentEffect
if (parentEffect) {
warn("[createEffect]: Nesting effects is not recommended.")
}
try {
currentEffect = wrapper
fn()
} finally {
currentEffect = parentEffect
}
}
wrapper()
}
export type Signal<T> = [get: () => T, set: (value: T) => void]
export function createSignal<T>(initialValue: T): Signal<T> {
let value = initialValue
const tracking: VoidFunctionSet = new Set()
return [
() => {
if (currentEffect) {
tracking.add(currentEffect)
}
return value
},
(newValue) => {
value = newValue
tracking.forEach(safeCall)
},
]
}