UNPKG

3.97 kBPlain TextView Raw
1import { Reaction } from "mobx"
2import {
3 ReactionCleanupTracking,
4 IReactionTracking,
5 CLEANUP_TIMER_LOOP_MILLIS,
6 createTrackingData
7} from "./reactionCleanupTrackingCommon"
8
9/**
10 * timers, gc-style, uncommitted reaction cleanup
11 */
12export function createTimerBasedReactionCleanupTracking(): ReactionCleanupTracking {
13 /**
14 * Reactions created by components that have yet to be fully mounted.
15 */
16 const uncommittedReactionRefs: Set<React.MutableRefObject<IReactionTracking | null>> = new Set()
17
18 /**
19 * Latest 'uncommitted reactions' cleanup timer handle.
20 */
21 let reactionCleanupHandle: ReturnType<typeof setTimeout> | undefined
22
23 /* istanbul ignore next */
24 /**
25 * Only to be used by test functions; do not export outside of mobx-react-lite
26 */
27 function forceCleanupTimerToRunNowForTests() {
28 // This allows us to control the execution of the cleanup timer
29 // to force it to run at awkward times in unit tests.
30 if (reactionCleanupHandle) {
31 clearTimeout(reactionCleanupHandle)
32 cleanUncommittedReactions()
33 }
34 }
35
36 /* istanbul ignore next */
37 function resetCleanupScheduleForTests() {
38 if (uncommittedReactionRefs.size > 0) {
39 for (const ref of uncommittedReactionRefs) {
40 const tracking = ref.current
41 if (tracking) {
42 tracking.reaction.dispose()
43 ref.current = null
44 }
45 }
46 uncommittedReactionRefs.clear()
47 }
48
49 if (reactionCleanupHandle) {
50 clearTimeout(reactionCleanupHandle)
51 reactionCleanupHandle = undefined
52 }
53 }
54
55 function ensureCleanupTimerRunning() {
56 if (reactionCleanupHandle === undefined) {
57 reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS)
58 }
59 }
60
61 function scheduleCleanupOfReactionIfLeaked(
62 ref: React.MutableRefObject<IReactionTracking | null>
63 ) {
64 uncommittedReactionRefs.add(ref)
65
66 ensureCleanupTimerRunning()
67 }
68
69 function recordReactionAsCommitted(
70 reactionRef: React.MutableRefObject<IReactionTracking | null>
71 ) {
72 uncommittedReactionRefs.delete(reactionRef)
73 }
74
75 /**
76 * Run by the cleanup timer to dispose any outstanding reactions
77 */
78 function cleanUncommittedReactions() {
79 reactionCleanupHandle = undefined
80
81 // Loop through all the candidate leaked reactions; those older
82 // than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied.
83
84 const now = Date.now()
85 uncommittedReactionRefs.forEach(ref => {
86 const tracking = ref.current
87 if (tracking) {
88 if (now >= tracking.cleanAt) {
89 // It's time to tidy up this leaked reaction.
90 tracking.reaction.dispose()
91 ref.current = null
92 uncommittedReactionRefs.delete(ref)
93 }
94 }
95 })
96
97 if (uncommittedReactionRefs.size > 0) {
98 // We've just finished a round of cleanups but there are still
99 // some leak candidates outstanding.
100 ensureCleanupTimerRunning()
101 }
102 }
103
104 return {
105 addReactionToTrack(
106 reactionTrackingRef: React.MutableRefObject<IReactionTracking | null>,
107 reaction: Reaction,
108 /**
109 * On timer based implementation we don't really need this object,
110 * but we keep the same api
111 */
112 objectRetainedByReact: unknown
113 ) {
114 reactionTrackingRef.current = createTrackingData(reaction)
115 scheduleCleanupOfReactionIfLeaked(reactionTrackingRef)
116 return reactionTrackingRef.current
117 },
118 recordReactionAsCommitted,
119 forceCleanupTimerToRunNowForTests,
120 resetCleanupScheduleForTests
121 }
122}