11import type { Fiber , FiberRoot } from 'react-reconciler' ;
2- import { NO_OP } from './utils' ;
2+ import * as React from 'react' ;
3+ import { didChange , NO_OP } from './utils' ;
34import type { Renderer } from './types' ;
45
56const PerformedWorkFlag = 0b01 ;
@@ -10,6 +11,11 @@ const ForwardRefTag = 11;
1011const MemoComponentTag = 14 ;
1112const SimpleMemoComponentTag = 15 ;
1213
14+ const ReactSharedInternals =
15+ ( React as any )
16+ ?. __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ||
17+ ( React as any ) ?. __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ;
18+
1319export const didFiberRender = ( fiber : Fiber | null ) : boolean => {
1420 if ( ! fiber ) return true ; // mount (probably)
1521 const prevProps = fiber . alternate ?. memoizedProps || { } ;
@@ -129,83 +135,110 @@ export const registerDevtoolsHook = ({
129135 onCommitFiberRoot ( rendererID , root ) ;
130136 } ;
131137
132- // const renderersArray = Array.from(devtoolsHook.renderers.values());
133- // for (let i = 0, len = renderersArray.length; i < len; i++) {
134- // const renderer = renderersArray[i];
135- // controlDispatcherRef(renderer.currentDispatcherRef);
136- // }
138+ if ( ReactSharedInternals ) {
139+ controlDispatcherRef ( ReactSharedInternals ) ;
140+ }
137141
138142 return devtoolsHook ;
139143} ;
140144
141- // TODO: check useMemo / useCallback / useMemoCache (React Compiler)
142-
143- // const REACT_MAJOR_VERSION = Number(React.version.split('.')[0]);
144- // const dispatcherRefs = new Set();
145-
146- // export const controlDispatcherRef = (currentDispatcherRef: any) => {
147- // const ref = currentDispatcherRef;
148- // if (ref && !dispatcherRefs.has(ref)) {
149- // // Renamed to ".H" in React 19
150- // const propName = REACT_MAJOR_VERSION > 18 ? 'H' : 'current';
151- // let currentDispatcher = ref[propName];
152- // const seenDispatchers = new Set();
153-
154- // Object.defineProperty(ref, propName, {
155- // get: () => currentDispatcher,
156- // set(current: any) {
157- // currentDispatcher = current;
158-
159- // if (
160- // !current ||
161- // seenDispatchers.has(current) ||
162- // current.useRef === current.useImperativeHandle ||
163- // /warnInvalidContextAccess\(\)/.test(current.readContext.toString())
164- // ) {
165- // return;
166- // }
167- // seenDispatchers.add(current);
168- // const isInComponent = peekIsInComponent(current);
169- // if (!isInComponent) return;
170- // const prevUseCallback = current.useCallback;
171- // const useCallback = (fn: (...args: any[]) => any, deps: any[]) => {
172- // return prevUseCallback(fn, deps);
173- // };
174- // current.useCallback = useCallback;
175-
176- // const prevUseMemo = current.useMemo;
177- // const useMemo = (fn: (...args: any[]) => any, deps: any[]) => {
178- // return prevUseMemo(fn, deps);
179- // };
180- // current.useMemo = useMemo;
181- // },
182- // });
183- // dispatcherRefs.add(ref);
184- // }
185- // };
186-
187- // const invalidHookErrFunctions = new WeakMap<() => void, boolean>();
188-
189- // /**
190- // * Check if you can currently run hooks in a component. This avoids allocting
191- // * a new hook on the stack by "peeking." Note that this doesn't correctly handle some cases
192- // * For example, if you are iterating through Array.map, it won't check if you allocate more/less hooks between renders
193- // *
194- // * This function checks the current dispatcher, which is swapped with an invalid / valid state by React. If
195- // * the current dispatcher is invalid (includes the string ("Error")), it will return false.
196- // */
197- // export const peekIsInComponent = (
198- // dispatcher: Record<string, () => void>,
199- // ): boolean => {
200- // const hook = dispatcher.useRef;
201-
202- // if (typeof hook !== 'function' || invalidHookErrFunctions.has(hook)) {
203- // return false;
204- // }
205- // const str = hook.toString();
206- // if (str.includes('Error')) {
207- // invalidHookErrFunctions.set(hook, true);
208- // return false;
209- // }
210- // return true;
211- // };
145+ const REACT_MAJOR_VERSION = Number ( React . version . split ( '.' ) [ 0 ] ) ;
146+ const dispatcherRefs = new Set ( ) ;
147+
148+ export const controlDispatcherRef = ( currentDispatcherRef : any ) => {
149+ const ref = currentDispatcherRef ;
150+ if ( ref && ! dispatcherRefs . has ( ref ) ) {
151+ // Renamed to ".H" in React 19
152+ const propName = REACT_MAJOR_VERSION > 18 ? 'H' : 'current' ;
153+ let currentDispatcher = ref [ propName ] ;
154+ const seenDispatchers = new Set ( ) ;
155+
156+ const callbackCache = new Map < string , any [ ] > ( ) ;
157+ const memoCache = new Map < string , any [ ] > ( ) ;
158+
159+ Object . defineProperty ( ref , propName , {
160+ get : ( ) => currentDispatcher ,
161+ set ( current : any ) {
162+ currentDispatcher = current ;
163+
164+ if (
165+ ! current ||
166+ seenDispatchers . has ( current ) ||
167+ current . useRef === current . useImperativeHandle ||
168+ / w a r n I n v a l i d C o n t e x t A c c e s s \( \) / . test ( current . readContext . toString ( ) )
169+ ) {
170+ return ;
171+ }
172+ seenDispatchers . add ( current ) ;
173+ const isInComponent = peekIsInComponent ( current ) ;
174+ if ( ! isInComponent ) return ;
175+ const prevUseCallback = current . useCallback ;
176+ const useCallback = ( fn : ( ...args : any [ ] ) => any , deps : any [ ] ) => {
177+ try {
178+ const key = fn . toString ( ) ;
179+ const prevDeps = callbackCache . get ( key ) ;
180+ if ( prevDeps && prevDeps . length === deps . length ) {
181+ for ( let i = 0 ; i < prevDeps . length ; i ++ ) {
182+ const changed = didChange ( prevDeps [ i ] , deps [ i ] ) ;
183+ if ( ! changed ) break ;
184+ // do something
185+ }
186+ }
187+ callbackCache . set ( key , deps ) ;
188+ } catch ( _err ) {
189+ /**/
190+ }
191+ return prevUseCallback ( fn , deps ) ;
192+ } ;
193+ current . useCallback = useCallback ;
194+
195+ const prevUseMemo = current . useMemo ;
196+ const useMemo = ( fn : ( ...args : any [ ] ) => any , deps : any [ ] ) => {
197+ try {
198+ const key = fn . toString ( ) ;
199+ const prevDeps = callbackCache . get ( key ) ;
200+ if ( prevDeps && prevDeps . length === deps . length ) {
201+ for ( let i = 0 ; i < prevDeps . length ; i ++ ) {
202+ const changed = didChange ( prevDeps [ i ] , deps [ i ] ) ;
203+ if ( ! changed ) break ;
204+ // do something
205+ }
206+ }
207+ memoCache . set ( key , deps ) ;
208+ } catch ( _err ) {
209+ /**/
210+ }
211+ return prevUseMemo ( fn , deps ) ;
212+ } ;
213+ current . useMemo = useMemo ;
214+ } ,
215+ } ) ;
216+ dispatcherRefs . add ( ref ) ;
217+ }
218+ } ;
219+
220+ const invalidHookErrFunctions = new WeakMap < ( ) => void , boolean > ( ) ;
221+
222+ /**
223+ * Check if you can currently run hooks in a component. This avoids allocting
224+ * a new hook on the stack by "peeking." Note that this doesn't correctly handle some cases
225+ * For example, if you are iterating through Array.map, it won't check if you allocate more/less hooks between renders
226+ *
227+ * This function checks the current dispatcher, which is swapped with an invalid / valid state by React. If
228+ * the current dispatcher is invalid (includes the string ("Error")), it will return false.
229+ */
230+ export const peekIsInComponent = (
231+ dispatcher : Record < string , ( ) => void > ,
232+ ) : boolean => {
233+ const hook = dispatcher . useRef ;
234+
235+ if ( typeof hook !== 'function' || invalidHookErrFunctions . has ( hook ) ) {
236+ return false ;
237+ }
238+ const str = hook . toString ( ) ;
239+ if ( str . includes ( 'Error' ) ) {
240+ invalidHookErrFunctions . set ( hook , true ) ;
241+ return false ;
242+ }
243+ return true ;
244+ } ;
0 commit comments