-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.js
178 lines (152 loc) · 4.54 KB
/
index.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
const getNewKeySelector = fns => {
const res = (...args) =>
fns.map(fn => encodeURIComponent(fn(...args))).join('/')
res.fns = fns
return res
}
const combinedKeySelectors = {}
const getCombinedKeySelector = keySelectors => {
const len = keySelectors.length
if (!combinedKeySelectors[len]) combinedKeySelectors[len] = []
const entry = combinedKeySelectors[len].find(([candidates]) =>
candidates.every((fn, idx) => keySelectors[idx] === fn)
)
if (entry) return entry[1]
const fn = getNewKeySelector(keySelectors)
combinedKeySelectors[len].push([keySelectors, fn])
return fn
}
const getKeySelector = dependencies => {
const keySelectorsSet = new WeakSet()
const keySelectors = []
dependencies.forEach(({ keySelector }) => {
if (!keySelector || keySelectorsSet.has(keySelector)) return
keySelectors.push(keySelector)
keySelectorsSet.add(keySelector)
})
if (keySelectors.length === 0) return null
return keySelectors.length === 1
? keySelectors[0]
: getCombinedKeySelector(keySelectors.sort())
}
const getComputeFn = (dependencies, computeFn, keySelector, getCache) => {
let nComputations = 0
if (!getCache) {
const cache = new Array(2)
getCache = () => cache
}
const computeFnCached = (computeFnArgs, key) => {
const cache = getCache(key)
const [prevArgs, prevRes] = cache
if (prevArgs && computeFnArgs.every((val, idx) => val === prevArgs[idx])) {
return prevRes
}
nComputations++
const res = computeFn(...computeFnArgs)
cache[0] = computeFnArgs
cache[1] = res
return res
}
const resFn = keySelector
? (...args) =>
computeFnCached(
dependencies.map(fn => fn(...args)),
keySelector(...args)
)
: (...args) => computeFnCached(dependencies.map(fn => fn(...args)))
resFn.recomputations = () => nComputations
resFn.resetRecomputations = () => (nComputations = 0)
resFn.dependencies = dependencies
resFn.resultFunc = computeFn
resFn.resultFuncCached = computeFnCached
resFn.resultFuncCached.recomputations = resFn.recomputations
return resFn
}
const getInstanceSelector = (
dependencies,
computeFn,
keySelector,
cache = new Map()
) => {
const usages = new Map()
const result = getComputeFn(dependencies, computeFn, keySelector, key => {
if (!cache.has(key)) cache.set(key, new Array(2))
return cache.get(key)
})
result.keySelector = keySelector
const inc = key => usages.set(key, (usages.get(key) || 0) + 1)
const dec = key => {
const count = usages.get(key)
if (count === undefined) return
if (count === 1) {
cache.delete(key)
usages.delete(key)
} else {
usages.set(key, count - 1)
}
}
const usableDependencies = dependencies.filter(d => d.use)
result.use = key => {
inc(key)
let dependantUsages
if (keySelector.fns) {
const keys = key.split('/').map(decodeURIComponent)
dependantUsages = usableDependencies.map(x =>
x.use(keys[keySelector.fns.indexOf(x.keySelector)])
)
} else {
dependantUsages = usableDependencies.map(x => x.use(key))
}
return () => {
dec(key)
dependantUsages.forEach(stop => stop())
}
}
result.clearCache = (recursive = true) => {
cache.clear()
usages.clear()
if (recursive) usableDependencies.forEach(x => x.clearCache())
}
return result
}
const getDependencies = args =>
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
export const createSelector = (...args) => {
const [computeFn] = args.splice(-1)
const dependencies = getDependencies(args)
const keySelector = getKeySelector(dependencies)
const getSelector = keySelector ? getInstanceSelector : getComputeFn
return getSelector(dependencies, computeFn, keySelector)
}
export const createKeyedSelectorFactory = (...args) => {
const [computeFn] = args.splice(-1)
const dependencies = getDependencies(args)
const cache = new Map()
return fn => {
const keySelector = createKeySelector(fn)
return getInstanceSelector(
[...dependencies, keySelector],
computeFn,
keySelector,
cache
)
}
}
export const createKeySelector = fn => {
const res = (s, ...args) => fn(...args)
res.keySelector = res
return res
}
export const createStructuredSelector = obj => {
const keys = Object.keys(obj)
const compute = (...vals) => {
const res = {}
vals.forEach((val, idx) => (res[keys[idx]] = val))
return res
}
return createSelector(
Object.values(obj),
compute
)
}
export const isInstanceSelector = sel => sel.keySelector && sel.use