-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
Subscription.ts
151 lines (126 loc) · 3.21 KB
/
Subscription.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { getBatch } from './batch'
// encapsulates the subscription logic for connecting a component to the redux store, as
// well as nesting subscriptions of descendant components, so that we can ensure the
// ancestor components re-render before descendants
type VoidFunc = () => void
type Listener = {
callback: VoidFunc
next: Listener | null
prev: Listener | null
}
function createListenerCollection() {
const batch = getBatch()
let first: Listener | null = null
let last: Listener | null = null
return {
clear() {
first = null
last = null
},
notify() {
batch(() => {
let listener = first
while (listener) {
listener.callback()
listener = listener.next
}
})
},
get() {
let listeners = []
let listener = first
while (listener) {
listeners.push(listener)
listener = listener.next
}
return listeners
},
subscribe(callback: () => void) {
let isSubscribed = true
let listener: Listener = (last = {
callback,
next: null,
prev: last,
})
if (listener.prev) {
listener.prev.next = listener
} else {
first = listener
}
return function unsubscribe() {
if (!isSubscribed || first === null) return
isSubscribed = false
if (listener.next) {
listener.next.prev = listener.prev
} else {
last = listener.prev
}
if (listener.prev) {
listener.prev.next = listener.next
} else {
first = listener.next
}
}
},
}
}
type ListenerCollection = ReturnType<typeof createListenerCollection>
export interface Subscription {
addNestedSub: (listener: VoidFunc) => VoidFunc
notifyNestedSubs: VoidFunc
handleChangeWrapper: VoidFunc
isSubscribed: () => boolean
onStateChange?: VoidFunc | null
trySubscribe: VoidFunc
tryUnsubscribe: VoidFunc
getListeners: () => ListenerCollection
}
const nullListeners = {
notify() {},
get: () => [],
} as unknown as ListenerCollection
export function createSubscription(store: any, parentSub?: Subscription) {
let unsubscribe: VoidFunc | undefined
let listeners: ListenerCollection = nullListeners
function addNestedSub(listener: () => void) {
trySubscribe()
return listeners.subscribe(listener)
}
function notifyNestedSubs() {
listeners.notify()
}
function handleChangeWrapper() {
if (subscription.onStateChange) {
subscription.onStateChange()
}
}
function isSubscribed() {
return Boolean(unsubscribe)
}
function trySubscribe() {
if (!unsubscribe) {
unsubscribe = parentSub
? parentSub.addNestedSub(handleChangeWrapper)
: store.subscribe(handleChangeWrapper)
listeners = createListenerCollection()
}
}
function tryUnsubscribe() {
if (unsubscribe) {
unsubscribe()
unsubscribe = undefined
listeners.clear()
listeners = nullListeners
}
}
const subscription: Subscription = {
addNestedSub,
notifyNestedSubs,
handleChangeWrapper,
isSubscribed,
trySubscribe,
tryUnsubscribe,
getListeners: () => listeners,
}
return subscription
}