mobx-react-lite
Version:
Lightweight React bindings for MobX based on function components and Hooks
132 lines (111 loc) • 4.72 kB
text/typescript
export declare class FinalizationRegistryType<T> {
constructor(finalize: (value: T) => void)
register(target: object, value: T, token?: object): void
unregister(token: object): void
}
declare const FinalizationRegistry: typeof FinalizationRegistryType | undefined
export const REGISTRY_FINALIZE_AFTER = 10_000
export const REGISTRY_SWEEP_INTERVAL = 10_000
export class TimerBasedFinalizationRegistry<T> implements FinalizationRegistryType<T> {
private registrations: Map<unknown, { value: T; registeredAt: number }> = new Map()
private sweepTimeout: ReturnType<typeof setTimeout> | undefined
constructor(private readonly finalize: (value: T) => void) {}
// Token is actually required with this impl
register(target: object, value: T, token?: object) {
this.registrations.set(token, {
value,
registeredAt: Date.now()
})
this.scheduleSweep()
}
unregister(token: unknown) {
this.registrations.delete(token)
if (this.registrations.size === 0) {
clearTimeout(this.sweepTimeout)
this.sweepTimeout = undefined
}
}
sweep = (maxAge = REGISTRY_FINALIZE_AFTER) => {
// cancel timeout so we can force sweep anytime
clearTimeout(this.sweepTimeout)
this.sweepTimeout = undefined
const now = Date.now()
this.registrations.forEach((registration, token) => {
if (now - registration.registeredAt >= maxAge) {
this.registrations.delete(token)
this.finalize(registration.value)
}
})
if (this.registrations.size > 0) {
this.scheduleSweep()
}
}
finalizeAllImmediately = () => {
this.sweep(0)
}
private scheduleSweep() {
if (this.sweepTimeout === undefined) {
this.sweepTimeout = setTimeout(this.sweep, REGISTRY_SWEEP_INTERVAL)
}
}
}
type Registration<T> = { value: T; token: object }
// Native finalization can run early, but cannot collect a target retained by its
// reaction's dependencies (for example, a computed capturing a React state setter).
// Keep the timer as a backstop even when native finalization is available.
export class FinalizationRegistryWithTimer<T> implements FinalizationRegistryType<T> {
private finalizeRegistration = (registration: Registration<T>) => {
// Remove both registrations before disposal, which can invoke user callbacks.
this.native?.unregister(registration.token)
this.timer.unregister(registration.token)
this.finalize(registration.value)
}
private tokens = new WeakMap<object, object>()
private staged = new Map<object, { target: object; value: T }>()
private flushScheduled = false
private native =
typeof FinalizationRegistry !== "undefined"
? new FinalizationRegistry<Registration<T>>(this.finalizeRegistration)
: undefined
private timer = new TimerBasedFinalizationRegistry<Registration<T>>(this.finalizeRegistration)
constructor(private readonly finalize: (value: T) => void) {}
register(target: object, value: T, token: object = target) {
this.unregister(token)
// Stage registrations so synchronous subscriptions avoid native and timer work.
this.staged.set(token, { target, value })
if (!this.flushScheduled) {
this.flushScheduled = true
Promise.resolve().then(this.flushRegistrations)
}
}
unregister(token: object) {
if (this.staged.delete(token)) {
return
}
const internalToken = this.tokens.get(token)
if (internalToken) {
this.tokens.delete(token)
this.native?.unregister(internalToken)
this.timer.unregister(internalToken)
}
}
finalizeAllImmediately = () => {
this.flushRegistrations()
this.timer.finalizeAllImmediately()
}
private flushRegistrations = () => {
this.flushScheduled = false
this.staged.forEach(({ target, value }, token) => {
// Use a separate token so a target used as its unregister token is not retained by the timer.
const registration = { value, token: {} }
this.tokens.set(token, registration.token)
this.native?.register(target, registration, registration.token)
this.timer.register(target, registration, registration.token)
})
this.staged.clear()
}
}
export const UniversalFinalizationRegistry =
typeof FinalizationRegistry !== "undefined"
? FinalizationRegistryWithTimer
: TimerBasedFinalizationRegistry