@@ -19,12 +19,15 @@ import type {FiberRoot} from './ReactFiberRoot';
1919import type { ExpirationTime } from './ReactFiberExpirationTime' ;
2020import type { CapturedValue , CapturedError } from './ReactCapturedValue' ;
2121import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
22+ import type { FunctionComponentUpdateQueue } from './ReactFiberHooks' ;
2223
2324import {
2425 enableSchedulerTracing ,
2526 enableProfilerTimer ,
2627} from 'shared/ReactFeatureFlags' ;
2728import {
29+ FunctionComponent ,
30+ ForwardRef ,
2831 ClassComponent ,
2932 HostRoot ,
3033 HostComponent ,
@@ -180,6 +183,22 @@ function safelyDetachRef(current: Fiber) {
180183 }
181184}
182185
186+ function safelyCallDestroy ( current , destroy ) {
187+ if ( __DEV__ ) {
188+ invokeGuardedCallback ( null , destroy , null ) ;
189+ if ( hasCaughtError ( ) ) {
190+ const error = clearCaughtError ( ) ;
191+ captureCommitPhaseError ( current , error ) ;
192+ }
193+ } else {
194+ try {
195+ destroy ( ) ;
196+ } catch ( error ) {
197+ captureCommitPhaseError ( current , error ) ;
198+ }
199+ }
200+ }
201+
183202function commitBeforeMutationLifeCycles (
184203 current : Fiber | null ,
185204 finishedWork : Fiber ,
@@ -235,13 +254,145 @@ function commitBeforeMutationLifeCycles(
235254 }
236255}
237256
257+ function destroyRemainingEffects ( firstToDestroy , stopAt ) {
258+ let effect = firstToDestroy ;
259+ do {
260+ const destroy = effect . value ;
261+ if ( destroy !== null ) {
262+ destroy ( ) ;
263+ }
264+ effect = effect . next ;
265+ } while ( effect !== stopAt ) ;
266+ }
267+
268+ function destroyMountedEffects ( current ) {
269+ const oldUpdateQueue : FunctionComponentUpdateQueue | null = ( current . updateQueue : any ) ;
270+ if ( oldUpdateQueue !== null ) {
271+ const oldLastEffect = oldUpdateQueue . lastEffect ;
272+ if ( oldLastEffect !== null ) {
273+ const oldFirstEffect = oldLastEffect . next ;
274+ destroyRemainingEffects ( oldFirstEffect , oldFirstEffect ) ;
275+ }
276+ }
277+ }
278+
238279function commitLifeCycles (
239280 finishedRoot : FiberRoot ,
240281 current : Fiber | null ,
241282 finishedWork : Fiber ,
242283 committedExpirationTime : ExpirationTime ,
243284) : void {
244285 switch ( finishedWork . tag ) {
286+ case FunctionComponent :
287+ case ForwardRef : {
288+ const updateQueue : FunctionComponentUpdateQueue | null = ( finishedWork . updateQueue : any ) ;
289+ if ( updateQueue !== null ) {
290+ // Mount new effects and destroy the old ones by comparing to the
291+ // current list of effects. This could be a bit simpler if we avoided
292+ // the need to compare to the previous effect list by transferring the
293+ // old `destroy` method to the new effect during the render phase.
294+ // That's how I originally implemented it, but it requires an additional
295+ // field on the effect object.
296+ //
297+ // This supports removing effects from the end of the list. If we adopt
298+ // the constraint that hooks are append only, that would also save a bit
299+ // on code size.
300+ const newLastEffect = updateQueue . lastEffect ;
301+ if ( newLastEffect !== null ) {
302+ const newFirstEffect = newLastEffect . next ;
303+ let oldLastEffect = null ;
304+ if ( current !== null ) {
305+ const oldUpdateQueue : FunctionComponentUpdateQueue | null = ( current . updateQueue : any ) ;
306+ if ( oldUpdateQueue !== null ) {
307+ oldLastEffect = oldUpdateQueue . lastEffect ;
308+ }
309+ }
310+ if ( oldLastEffect !== null ) {
311+ const oldFirstEffect = oldLastEffect . next ;
312+ let newEffect = newFirstEffect ;
313+ let oldEffect = oldFirstEffect ;
314+
315+ // Before mounting the new effects, unmount all the old ones.
316+ do {
317+ if ( oldEffect !== null ) {
318+ if ( newEffect . inputs !== oldEffect . inputs ) {
319+ const destroy = oldEffect . value ;
320+ if ( destroy !== null ) {
321+ destroy ( ) ;
322+ }
323+ }
324+ oldEffect = oldEffect . next ;
325+ if ( oldEffect === oldFirstEffect ) {
326+ oldEffect = null ;
327+ }
328+ }
329+ newEffect = newEffect . next ;
330+ } while ( newEffect !== newFirstEffect ) ;
331+
332+ // Unmount any remaining effects in the old list that do not
333+ // appear in the new one.
334+ if ( oldEffect !== null ) {
335+ destroyRemainingEffects ( oldEffect , oldFirstEffect ) ;
336+ }
337+
338+ // Now loop through the list again to mount the new effects
339+ oldEffect = oldFirstEffect ;
340+ do {
341+ const create = newEffect . value ;
342+ if ( oldEffect !== null ) {
343+ if ( newEffect . inputs !== oldEffect . inputs ) {
344+ const newDestroy = create ( ) ;
345+ newEffect . value =
346+ typeof newDestroy === 'function' ? newDestroy : null ;
347+ } else {
348+ newEffect . value = oldEffect . value ;
349+ }
350+ oldEffect = oldEffect . next ;
351+ if ( oldEffect === oldFirstEffect ) {
352+ oldEffect = null ;
353+ }
354+ } else {
355+ const newDestroy = create ( ) ;
356+ newEffect . value =
357+ typeof newDestroy === 'function' ? newDestroy : null ;
358+ }
359+ newEffect = newEffect . next ;
360+ } while ( newEffect !== newFirstEffect ) ;
361+ } else {
362+ let newEffect = newFirstEffect ;
363+ do {
364+ const create = newEffect . value ;
365+ const newDestroy = create ( ) ;
366+ newEffect . value =
367+ typeof newDestroy === 'function' ? newDestroy : null ;
368+ newEffect = newEffect . next ;
369+ } while ( newEffect !== newFirstEffect ) ;
370+ }
371+ } else if ( current !== null ) {
372+ // There are no effects, which means all current effects must
373+ // be destroyed
374+ destroyMountedEffects ( current ) ;
375+ }
376+
377+ const callbackList = updateQueue . callbackList ;
378+ if ( callbackList !== null ) {
379+ updateQueue . callbackList = null ;
380+ for ( let i = 0 ; i < callbackList . length ; i ++ ) {
381+ const update = callbackList [ i ] ;
382+ // Assume this is non-null, since otherwise it would not be part
383+ // of the callback list.
384+ const callback : ( ) => mixed = ( update . callback : any ) ;
385+ update . callback = null ;
386+ callback ( ) ;
387+ }
388+ }
389+ } else if ( current !== null ) {
390+ // There are no effects, which means all current effects must
391+ // be destroyed
392+ destroyMountedEffects ( current ) ;
393+ }
394+ break ;
395+ }
245396 case ClassComponent: {
246397 const instance = finishedWork . stateNode ;
247398 if ( finishedWork . effectTag & Update ) {
@@ -496,6 +647,25 @@ function commitUnmount(current: Fiber): void {
496647 onCommitUnmount ( current ) ;
497648
498649 switch ( current . tag ) {
650+ case FunctionComponent :
651+ case ForwardRef : {
652+ const updateQueue : FunctionComponentUpdateQueue | null = ( current . updateQueue : any ) ;
653+ if ( updateQueue !== null ) {
654+ const lastEffect = updateQueue . lastEffect ;
655+ if ( lastEffect !== null ) {
656+ const firstEffect = lastEffect . next ;
657+ let effect = firstEffect ;
658+ do {
659+ const destroy = effect . value ;
660+ if ( destroy !== null ) {
661+ safelyCallDestroy ( current , destroy ) ;
662+ }
663+ effect = effect . next ;
664+ } while ( effect !== firstEffect ) ;
665+ }
666+ }
667+ break ;
668+ }
499669 case ClassComponent: {
500670 safelyDetachRef ( current ) ;
501671 const instance = current . stateNode ;
0 commit comments