From bceb786c5aad621d9f6c2ee4dd3c04fe921919bb Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Wed, 17 May 2023 14:39:26 -0400 Subject: [PATCH] [runtime] useRenderCounter --- (`react-forget-runtime` package seems to be synced to -.) RFC: useRenderCounter hook: - tracks # renders (increments on render path) - exposes a global renderCounterRegistry (counting # renders in alive / mounted components) Next PR will modify BabelPlugin to add codegen ```js // Similar to how we're currently importing `isForgetEnabled` import {isInstrumentForgetEnabled_Secret} from "ReactForgetFeatureFlag"; // ... function Component_uncompiled(props) { if (isInstrumentForgetEnabled_Secret) { useRenderCounter(); } // ... } function Component_forget(props) { if (isInstrumentForgetEnabled_Secret) { useRenderCounter(); } // ... } ``` --- .../packages/react-forget-runtime/index.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/compiler/forget/packages/react-forget-runtime/index.js b/compiler/forget/packages/react-forget-runtime/index.js index 20292f1c84..8c6f717773 100644 --- a/compiler/forget/packages/react-forget-runtime/index.js +++ b/compiler/forget/packages/react-forget-runtime/index.js @@ -12,6 +12,7 @@ const { ReactCurrentDispatcher, }, useRef, + useEffect, } = React; export const $empty = Symbol.for("react.memo_cache_sentinel"); @@ -89,3 +90,54 @@ export function $reset($) { export function $makeReadOnly() { throw new Error("TODO: implement $makeReadOnly in react-forget-runtime"); } + +/** + * Instrumentation to count rerenders in React components + */ +export const renderCounterRegistry /*: Map>*/ = + new Map(); +export function clearRenderCounterRegistry() { + for (const counters of renderCounterRegistry.values()) { + counters.forEach((counter) => { + counter.count = 0; + }); + } +} + +function registerRenderCounter(name, val) { + let counters = renderCounterRegistry.get(name); + if (counters == null) { + counters = new Set(); + renderCounterRegistry.set(name, counters); + } + counters.add(val); +} + +function removeRenderCounter(name, val): void { + const counters = renderCounterRegistry.get(name); + if (counters == null) { + return; + } + counters.delete(val); +} + +export function useRenderCounter(name): void { + const val = useRef(null); + + if (val.current != null) { + val.current.count += 1; + } + useEffect(() => { + // Not counting initial render shouldn't be a problem + if (val.current == null) { + const counter = { count: 0 }; + registerRenderCounter(name, counter); + val.current = counter; + } + return () => { + if (val.current !== null) { + removeRenderCounter(name, val.current); + } + }; + }); +}