1
1
import {
2
2
createAction ,
3
+ nanoid ,
3
4
PayloadAction ,
4
5
Middleware ,
5
6
Dispatch ,
@@ -16,8 +17,14 @@ interface BaseActionCreator<P, T extends string, M = never, E = never> {
16
17
interface TypedActionCreator < Type extends string > {
17
18
( ...args : any [ ] ) : Action < Type >
18
19
type : Type
20
+ match : MatchFunction < any >
19
21
}
20
22
23
+ type ListenerPredicate < Action extends AnyAction , State = unknown > = (
24
+ action : Action ,
25
+ state ?: State
26
+ ) => boolean
27
+
21
28
type MatchFunction < T > = ( v : any ) => v is T
22
29
23
30
export interface HasMatchFunction < T > {
@@ -192,16 +199,14 @@ export function createActionListenerMiddleware<
192
199
D extends Dispatch < AnyAction > = Dispatch
193
200
> ( ) {
194
201
type ListenerEntry = ActionListenerOptions & {
202
+ id : string
195
203
listener : ActionListener < any , S , D , any >
196
204
unsubscribe : ( ) => void
205
+ type ?: string
206
+ predicate : ListenerPredicate < any >
197
207
}
198
208
199
- type ListenerEntryWithMatcher = ListenerEntry & {
200
- matcher : MatchFunction < any >
201
- }
202
-
203
- const listenerMap : Record < string , Set < ListenerEntry > | undefined > = { }
204
- const matcherListeners = new Set < ListenerEntryWithMatcher > ( )
209
+ const listenerMap = new Map < string , ListenerEntry > ( )
205
210
206
211
const middleware : Middleware <
207
212
{
@@ -225,114 +230,59 @@ export function createActionListenerMiddleware<
225
230
226
231
return
227
232
}
228
- // @ts -ignore
229
- const listeners = listenerMap [ action . type ]
230
- let matchedMatcherListeners : ListenerEntry [ ] = [ ]
231
-
232
- if ( matcherListeners . size > 0 ) {
233
- matchedMatcherListeners = Array . from ( matcherListeners ) . filter ( ( entry ) => {
234
- return entry . matcher ( action )
235
- } )
236
- }
237
233
238
- if ( listeners || matcherListeners . size > 0 ) {
239
- const allListeners = Array . from ( listeners ?? [ ] ) . concat (
240
- matchedMatcherListeners
241
- )
242
- const defaultWhen = 'after'
243
- let result : unknown
244
- for ( const phase of [ 'before' , 'after' ] as const ) {
245
- for ( const entry of allListeners ) {
246
- if ( phase !== ( entry . when || defaultWhen ) ) {
247
- continue
248
- }
249
- let stoppedPropagation = false
250
- let currentPhase = phase
251
- let synchronousListenerFinished = false
252
- entry . listener ( action , {
253
- ...api ,
254
- stopPropagation ( ) {
255
- if ( currentPhase === 'before' ) {
256
- if ( ! synchronousListenerFinished ) {
257
- stoppedPropagation = true
258
- } else {
259
- throw new Error (
260
- 'stopPropagation can only be called synchronously'
261
- )
262
- }
234
+ let stateBefore = api . getState ( )
235
+
236
+ const defaultWhen : When = 'after'
237
+ let result : unknown
238
+ for ( const phase of [ 'before' , 'after' ] as const ) {
239
+ let stateNow = api . getState ( )
240
+ for ( let entry of listenerMap . values ( ) ) {
241
+ if (
242
+ ( entry . when || defaultWhen ) !== phase ||
243
+ ! entry . predicate ( action , stateNow )
244
+ ) {
245
+ continue
246
+ }
247
+
248
+ let stoppedPropagation = false
249
+ let currentPhase = phase
250
+ let synchronousListenerFinished = false
251
+ entry . listener ( action , {
252
+ ...api ,
253
+ stopPropagation ( ) {
254
+ if ( currentPhase === 'before' ) {
255
+ if ( ! synchronousListenerFinished ) {
256
+ stoppedPropagation = true
263
257
} else {
264
258
throw new Error (
265
- 'stopPropagation can only be called by action listeners with the `when` option set to "before" '
259
+ 'stopPropagation can only be called synchronously '
266
260
)
267
261
}
268
- } ,
269
- unsubscribe : entry . unsubscribe ,
270
- } )
271
- synchronousListenerFinished = true
272
- if ( stoppedPropagation ) {
273
- return action
274
- }
275
- }
276
- if ( phase === 'before' ) {
277
- result = next ( action )
278
- } else {
279
- return result
262
+ } else {
263
+ throw new Error (
264
+ 'stopPropagation can only be called by action listeners with the `when` option set to "before"'
265
+ )
266
+ }
267
+ } ,
268
+ unsubscribe : entry . unsubscribe ,
269
+ } )
270
+ synchronousListenerFinished = true
271
+ if ( stoppedPropagation ) {
272
+ return action
280
273
}
281
274
}
275
+ if ( phase === 'before' ) {
276
+ result = next ( action )
277
+ } else {
278
+ return result
279
+ }
282
280
}
283
281
return next ( action )
284
282
}
285
283
286
284
type Unsubscribe = ( ) => void
287
285
288
- function addStringListener < T extends string , O extends ActionListenerOptions > (
289
- type : T ,
290
- listener : ActionListener < Action < T > , S , D , O > ,
291
- options ?: O
292
- ) : Unsubscribe {
293
- const listeners = getListenerMap ( type )
294
- let entry = findListenerEntry ( listeners , listener )
295
-
296
- if ( ! entry ) {
297
- entry = {
298
- ...options ,
299
- listener,
300
- unsubscribe : ( ) => listeners . delete ( entry ! ) ,
301
- }
302
-
303
- listeners . add ( entry )
304
- }
305
-
306
- return entry . unsubscribe
307
- }
308
-
309
- function addMatcherListener <
310
- MA extends AnyAction ,
311
- M extends MatchFunction < MA > ,
312
- O extends ActionListenerOptions
313
- > (
314
- matcher : M ,
315
- listener : ActionListener < MA , S , D , O > ,
316
- options ?: O
317
- ) : Unsubscribe {
318
- let entry = findListenerEntry ( matcherListeners , listener ) as
319
- | ListenerEntryWithMatcher
320
- | undefined
321
-
322
- if ( ! entry ) {
323
- entry = {
324
- ...options ,
325
- listener,
326
- matcher,
327
- unsubscribe : ( ) => matcherListeners . delete ( entry ! ) ,
328
- }
329
-
330
- matcherListeners . add ( entry )
331
- }
332
-
333
- return entry . unsubscribe
334
- }
335
-
336
286
type GuardedType < T > = T extends ( x : any ) => x is infer T ? T : never
337
287
338
288
function addListener <
@@ -358,28 +308,55 @@ export function createActionListenerMiddleware<
358
308
matcher : M ,
359
309
listener : ActionListener < GuardedType < M > , S , D , O > ,
360
310
options ?: O
311
+ ) : Unsubscribe // eslint-disable-next-line no-redeclare
312
+ function addListener <
313
+ MA extends AnyAction ,
314
+ M extends ListenerPredicate < MA > ,
315
+ O extends ActionListenerOptions
316
+ > (
317
+ matcher : M ,
318
+ listener : ActionListener < AnyAction , S , D , O > ,
319
+ options ?: O
361
320
) : Unsubscribe
362
321
// eslint-disable-next-line no-redeclare
363
322
function addListener (
364
323
typeOrActionCreator : string | TypedActionCreator < any > ,
365
324
listener : ActionListener < AnyAction , S , D , any > ,
366
325
options ?: ActionListenerOptions
367
326
) : Unsubscribe {
368
- if ( typeof typeOrActionCreator === 'string' ) {
369
- return addStringListener ( typeOrActionCreator , listener , options )
370
- } else if ( typeof typeOrActionCreator . type === 'string' ) {
371
- return addStringListener ( typeOrActionCreator . type , listener , options )
372
- } else {
373
- const matcher = typeOrActionCreator as unknown as MatchFunction < any >
374
- return addMatcherListener ( matcher , listener , options )
375
- }
376
- }
327
+ let predicate : ListenerPredicate < any >
328
+ let type : string | undefined
377
329
378
- function getListenerMap ( type : string ) {
379
- if ( ! listenerMap [ type ] ) {
380
- listenerMap [ type ] = new Set ( )
330
+ let entry = findListenerEntry (
331
+ ( existingEntry ) => existingEntry . listener === listener
332
+ )
333
+
334
+ if ( ! entry ) {
335
+ if ( typeof typeOrActionCreator === 'string' ) {
336
+ type = typeOrActionCreator
337
+ predicate = ( action : any ) => action . type === type
338
+ } else if ( typeof typeOrActionCreator . type === 'string' ) {
339
+ type = typeOrActionCreator . type
340
+ predicate = typeOrActionCreator . match
341
+ } else {
342
+ predicate = typeOrActionCreator as unknown as ListenerPredicate < any >
343
+ }
344
+
345
+ const id = nanoid ( )
346
+ const unsubscribe = ( ) => listenerMap . delete ( id )
347
+ entry = {
348
+ ...options ,
349
+ id,
350
+ listener,
351
+ type,
352
+ predicate,
353
+ unsubscribe,
354
+ }
355
+
356
+ listenerMap . set ( id , entry )
381
357
}
382
- return listenerMap [ type ] !
358
+
359
+ return entry . unsubscribe
383
360
}
384
361
385
362
function removeListener < C extends TypedActionCreator < any > > (
@@ -401,31 +378,28 @@ export function createActionListenerMiddleware<
401
378
? typeOrActionCreator
402
379
: typeOrActionCreator . type
403
380
404
- const listeners = listenerMap [ type ]
405
-
406
- if ( ! listeners ) {
407
- return false
408
- }
409
-
410
- let entry = findListenerEntry ( listeners , listener )
381
+ let entry = findListenerEntry (
382
+ ( entry ) => entry . type === type && entry . listener === listener
383
+ )
411
384
412
385
if ( ! entry ) {
413
386
return false
414
387
}
415
388
416
- listeners . delete ( entry )
389
+ listenerMap . delete ( entry . id )
417
390
return true
418
391
}
419
392
420
393
function findListenerEntry (
421
- entries : Set < ListenerEntry > ,
422
- listener : Function
394
+ comparator : ( entry : ListenerEntry ) => boolean
423
395
) : ListenerEntry | undefined {
424
- for ( const entry of entries ) {
425
- if ( entry . listener === listener ) {
396
+ for ( const entry of listenerMap . values ( ) ) {
397
+ if ( comparator ( entry ) ) {
426
398
return entry
427
399
}
428
400
}
401
+
402
+ return undefined
429
403
}
430
404
431
405
return Object . assign (
0 commit comments