@@ -19,12 +19,15 @@ import type {FiberRoot} from './ReactFiberRoot';
19
19
import type { ExpirationTime } from './ReactFiberExpirationTime' ;
20
20
import type { CapturedValue , CapturedError } from './ReactCapturedValue' ;
21
21
import type { SuspenseState } from './ReactFiberSuspenseComponent' ;
22
+ import type { FunctionComponentUpdateQueue } from './ReactFiberHooks' ;
22
23
23
24
import {
24
25
enableSchedulerTracing ,
25
26
enableProfilerTimer ,
26
27
} from 'shared/ReactFeatureFlags' ;
27
28
import {
29
+ FunctionComponent ,
30
+ ForwardRef ,
28
31
ClassComponent ,
29
32
HostRoot ,
30
33
HostComponent ,
@@ -180,6 +183,22 @@ function safelyDetachRef(current: Fiber) {
180
183
}
181
184
}
182
185
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
+
183
202
function commitBeforeMutationLifeCycles (
184
203
current : Fiber | null ,
185
204
finishedWork : Fiber ,
@@ -235,13 +254,145 @@ function commitBeforeMutationLifeCycles(
235
254
}
236
255
}
237
256
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
+
238
279
function commitLifeCycles (
239
280
finishedRoot : FiberRoot ,
240
281
current : Fiber | null ,
241
282
finishedWork : Fiber ,
242
283
committedExpirationTime : ExpirationTime ,
243
284
) : void {
244
285
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
+ }
245
396
case ClassComponent: {
246
397
const instance = finishedWork . stateNode ;
247
398
if ( finishedWork . effectTag & Update ) {
@@ -496,6 +647,25 @@ function commitUnmount(current: Fiber): void {
496
647
onCommitUnmount ( current ) ;
497
648
498
649
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
+ }
499
669
case ClassComponent: {
500
670
safelyDetachRef ( current ) ;
501
671
const instance = current . stateNode ;
0 commit comments