From 2d5fe1c5b3d4e16a96f4e8f06d56eaf54eb807d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=9D=A8=E5=B8=86?= <39647285+leno23@users.noreply.github.com> Date: Sat, 16 May 2026 13:05:30 +0800 Subject: [PATCH] perf(devtools): scope Fast Refresh cache purges --- packages/react-devtools-shared/src/backend/agent.js | 6 +++--- .../src/backend/fiber/renderer.js | 7 ++----- packages/react-devtools-shared/src/bridge.js | 2 +- .../views/Components/InspectedElementContext.js | 12 ++++++++++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 18f3e208408b..2c850462e651 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -1109,12 +1109,12 @@ export default class Agent extends EventEmitter<{ this.emit('traceUpdates', nodes); }; - onFastRefreshScheduled: () => void = () => { + onFastRefreshScheduled: (rendererID: number | null | void) => void = rendererID => { if (__DEBUG__) { - debug('onFastRefreshScheduled'); + debug('onFastRefreshScheduled', rendererID); } - this._bridge.send('fastRefreshScheduled'); + this._bridge.send('fastRefreshScheduled', rendererID); }; onHookOperations: (operations: Array) => void = operations => { diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 037ce1c5cc3b..c5cf34c519e5 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -488,13 +488,10 @@ export function attach( if (typeof scheduleRefresh === 'function') { // When Fast Refresh updates a component, the frontend may need to purge cached information. // For example, ASTs cached for the component (for named hooks) may no longer be valid. - // Send a signal to the frontend to purge this cached information. - // The "fastRefreshScheduled" dispatched is global (not Fiber or even Renderer specific). - // This is less effecient since it means the front-end will need to purge the entire cache, - // but this is probably an okay trade off in order to reduce coupling between the DevTools and Fast Refresh. + // Send a signal to the frontend to purge cached information for this renderer. renderer.scheduleRefresh = (...args) => { try { - hook.emit('fastRefreshScheduled'); + hook.emit('fastRefreshScheduled', rendererID); } finally { return scheduleRefresh(...args); } diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index ba4b2a0f8061..bac9ba2f731b 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -201,7 +201,7 @@ export type BackendEvents = { backendVersion: [string], bridgeProtocol: [BridgeProtocol], extensionBackendInitialized: [], - fastRefreshScheduled: [], + fastRefreshScheduled: [number | null | void], getSavedPreferences: [], inspectedElement: [InspectedElementPayload], inspectedScreen: [InspectedElementPayload], diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js index f6549fc243e4..89cc539d6afa 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementContext.js @@ -180,7 +180,15 @@ export function InspectedElementContextController({ const purgeCachedMetadata = purgeCachedMetadataRef.current; if (typeof purgeCachedMetadata === 'function') { // When Fast Refresh updates a component, any cached AST metadata may be invalid. - const fastRefreshScheduled = () => { + const fastRefreshScheduled = (rendererID: number | null | void) => { + if ( + rendererID != null && + element !== null && + element.rendererID !== rendererID + ) { + return; + } + startTransition(() => { clearHookNamesCache(); purgeCachedMetadata(); @@ -191,7 +199,7 @@ export function InspectedElementContextController({ return () => bridge.removeListener('fastRefreshScheduled', fastRefreshScheduled); } - }, [bridge]); + }, [bridge, element]); // Reset path now that we've asked the backend to hydrate it. // The backend is stateful, so we don't need to remember this path the next time we inspect.