source: node_modules/@reduxjs/toolkit/src/autoBatchEnhancer.ts

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 5.1 KB
Line 
1import type { StoreEnhancer } from 'redux'
2
3export const SHOULD_AUTOBATCH = 'RTK_autoBatch'
4
5export const prepareAutoBatched =
6 <T>() =>
7 (payload: T): { payload: T; meta: unknown } => ({
8 payload,
9 meta: { [SHOULD_AUTOBATCH]: true },
10 })
11
12const createQueueWithTimer = (timeout: number) => {
13 return (notify: () => void) => {
14 setTimeout(notify, timeout)
15 }
16}
17
18export type AutoBatchOptions =
19 | { type: 'tick' }
20 | { type: 'timer'; timeout: number }
21 | { type: 'raf' }
22 | { type: 'callback'; queueNotification: (notify: () => void) => void }
23
24/**
25 * A Redux store enhancer that watches for "low-priority" actions, and delays
26 * notifying subscribers until either the queued callback executes or the
27 * next "standard-priority" action is dispatched.
28 *
29 * This allows dispatching multiple "low-priority" actions in a row with only
30 * a single subscriber notification to the UI after the sequence of actions
31 * is finished, thus improving UI re-render performance.
32 *
33 * Watches for actions with the `action.meta[SHOULD_AUTOBATCH]` attribute.
34 * This can be added to `action.meta` manually, or by using the
35 * `prepareAutoBatched` helper.
36 *
37 * By default, it will queue a notification for the end of the event loop tick.
38 * However, you can pass several other options to configure the behavior:
39 * - `{type: 'tick'}`: queues using `queueMicrotask`
40 * - `{type: 'timer', timeout: number}`: queues using `setTimeout`
41 * - `{type: 'raf'}`: queues using `requestAnimationFrame` (default)
42 * - `{type: 'callback', queueNotification: (notify: () => void) => void}`: lets you provide your own callback
43 *
44 *
45 */
46export const autoBatchEnhancer =
47 (options: AutoBatchOptions = { type: 'raf' }): StoreEnhancer =>
48 (next) =>
49 (...args) => {
50 const store = next(...args)
51
52 let notifying = true
53 let shouldNotifyAtEndOfTick = false
54 let notificationQueued = false
55
56 const listeners = new Set<() => void>()
57
58 const queueCallback =
59 options.type === 'tick'
60 ? queueMicrotask
61 : options.type === 'raf'
62 ? // requestAnimationFrame won't exist in SSR environments. Fall back to a vague approximation just to keep from erroring.
63 typeof window !== 'undefined' && window.requestAnimationFrame
64 ? window.requestAnimationFrame
65 : createQueueWithTimer(10)
66 : options.type === 'callback'
67 ? options.queueNotification
68 : createQueueWithTimer(options.timeout)
69
70 const notifyListeners = () => {
71 // We're running at the end of the event loop tick.
72 // Run the real listener callbacks to actually update the UI.
73 notificationQueued = false
74 if (shouldNotifyAtEndOfTick) {
75 shouldNotifyAtEndOfTick = false
76 listeners.forEach((l) => l())
77 }
78 }
79
80 return Object.assign({}, store, {
81 // Override the base `store.subscribe` method to keep original listeners
82 // from running if we're delaying notifications
83 subscribe(listener: () => void) {
84 // Each wrapped listener will only call the real listener if
85 // the `notifying` flag is currently active when it's called.
86 // This lets the base store work as normal, while the actual UI
87 // update becomes controlled by this enhancer.
88 const wrappedListener: typeof listener = () => notifying && listener()
89 const unsubscribe = store.subscribe(wrappedListener)
90 listeners.add(listener)
91 return () => {
92 unsubscribe()
93 listeners.delete(listener)
94 }
95 },
96 // Override the base `store.dispatch` method so that we can check actions
97 // for the `shouldAutoBatch` flag and determine if batching is active
98 dispatch(action: any) {
99 try {
100 // If the action does _not_ have the `shouldAutoBatch` flag,
101 // we resume/continue normal notify-after-each-dispatch behavior
102 notifying = !action?.meta?.[SHOULD_AUTOBATCH]
103 // If a `notifyListeners` microtask was queued, you can't cancel it.
104 // Instead, we set a flag so that it's a no-op when it does run
105 shouldNotifyAtEndOfTick = !notifying
106 if (shouldNotifyAtEndOfTick) {
107 // We've seen at least 1 action with `SHOULD_AUTOBATCH`. Try to queue
108 // a microtask to notify listeners at the end of the event loop tick.
109 // Make sure we only enqueue this _once_ per tick.
110 if (!notificationQueued) {
111 notificationQueued = true
112 queueCallback(notifyListeners)
113 }
114 }
115 // Go ahead and process the action as usual, including reducers.
116 // If normal notification behavior is enabled, the store will notify
117 // all of its own listeners, and the wrapper callbacks above will
118 // see `notifying` is true and pass on to the real listener callbacks.
119 // If we're "batching" behavior, then the wrapped callbacks will
120 // bail out, causing the base store notification behavior to be no-ops.
121 return store.dispatch(action)
122 } finally {
123 // Assume we're back to normal behavior after each action
124 notifying = true
125 }
126 },
127 })
128 }
Note: See TracBrowser for help on using the repository browser.