12
ReactContext,
13
ReactEventResponderListener,
14
} from 'shared/ReactTypes';
15
-import type {SideEffectTag} from 'shared/ReactSideEffectTags';
15
import type {Fiber} from './ReactFiber';
16
import type {ExpirationTime} from './ReactFiberExpirationTime';
17
import type {HookEffectTag} from './ReactHookEffectTags';
174
let renderExpirationTime: ExpirationTime = NoWork;
175
// The work-in-progress fiber. I've named it differently to distinguish it from
176
// the work-in-progress hook.
178
-let currentlyRenderingFiber: Fiber | null = null;
177
+let currentlyRenderingFiber: Fiber = (null: any);
178
179
// Hooks are stored as a linked list on the fiber's memoizedState field. The
180
// current hook list is the list that belongs to the current fiber. The
181
// work-in-progress hook list is a new list that will be added to the
182
// work-in-progress fiber.
183
let currentHook: Hook | null = null;
185
-let nextCurrentHook: Hook | null = null;
186
-let firstWorkInProgressHook: Hook | null = null;
184
let workInProgressHook: Hook | null = null;
188
-let nextWorkInProgressHook: Hook | null = null;
189
-
190
-let remainingExpirationTime: ExpirationTime = NoWork;
191
-let componentUpdateQueue: FunctionComponentUpdateQueue | null = null;
192
-let sideEffectTag: SideEffectTag = 0;
185
186
// Updates scheduled during render will trigger an immediate re-render at the
187
// end of the current pass. We can't store these updates on the normal queue,
198
UpdateQueue<any, any>,
199
Update<any, any>,
200
> | null = null;
209
-// Counter to prevent infinite loops.
210
-let numberOfReRenders: number = 0;
201
+
202
const RE_RENDER_LIMIT = 25;
203
204
// In DEV, this is the name of the currently executing primitive hook
258
259
function warnOnHookMismatchInDev(currentHookName: HookType) {
260
if (__DEV__) {
270
- const componentName = getComponentName(
271
- ((currentlyRenderingFiber: any): Fiber).type,
272
- );
261
+ const componentName = getComponentName(currentlyRenderingFiber.type);
262
if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) {
263
didWarnAboutMismatchedHooksForComponent.add(componentName);
264
375
): any {
376
renderExpirationTime = nextRenderExpirationTime;
377
currentlyRenderingFiber = workInProgress;
389
- nextCurrentHook = current !== null ? current.memoizedState : null;
378
379
if (__DEV__) {
380
hookTypesDev =
387
current !== null && current.type !== workInProgress.type;
388
}
389
390
+ workInProgress.memoizedState = null;
391
+ workInProgress.updateQueue = null;
392
+ workInProgress.expirationTime = NoWork;
393
+
394
// The following should have already been reset
395
// currentHook = null;
396
// workInProgressHook = null;
397
406
- // remainingExpirationTime = NoWork;
407
- // componentUpdateQueue = null;
408
-
398
// didScheduleRenderPhaseUpdate = false;
399
// renderPhaseUpdates = null;
411
- // numberOfReRenders = 0;
412
- // sideEffectTag = 0;
400
401
// TODO Warn if no hooks are used at all during mount, then some are used during update.
415
- // Currently we will identify the update render as a mount because nextCurrentHook === null.
402
+ // Currently we will identify the update render as a mount because memoizedState === null.
403
// This is tricky because it's valid for certain types of components (e.g. React.lazy)
404
418
- // Using nextCurrentHook to differentiate between mount/update only works if at least one stateful hook is used.
405
+ // Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
406
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
420
- // so nextCurrentHook would be null during updates and mounts.
407
+ // so memoizedState would be null during updates and mounts.
408
if (__DEV__) {
422
- if (nextCurrentHook !== null) {
409
+ if (current !== null && current.memoizedState !== null) {
410
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
411
} else if (hookTypesDev !== null) {
412
// This dispatcher handles an edge case where a component is updating,
420
}
421
} else {
422
ReactCurrentDispatcher.current =
436
- nextCurrentHook === null
423
+ current === null || current.memoizedState === null
424
? HooksDispatcherOnMount
425
: HooksDispatcherOnUpdate;
426
}
428
let children = Component(props, refOrContext);
429
430
if (didScheduleRenderPhaseUpdate) {
431
+ // Counter to prevent infinite loops.
432
+ let numberOfReRenders: number = 0;
433
do {
434
didScheduleRenderPhaseUpdate = false;
435
+
436
+ invariant(
437
+ numberOfReRenders < RE_RENDER_LIMIT,
438
+ 'Too many re-renders. React limits the number of renders to prevent ' +
439
+ 'an infinite loop.',
440
+ );
441
+
442
numberOfReRenders += 1;
443
if (__DEV__) {
444
// Even when hot reloading, allow dependencies to stabilize
447
}
448
449
// Start over from the beginning of the list
454
- nextCurrentHook = current !== null ? current.memoizedState : null;
455
- nextWorkInProgressHook = firstWorkInProgressHook;
456
-
450
currentHook = null;
451
workInProgressHook = null;
459
- componentUpdateQueue = null;
452
+
453
+ workInProgress.updateQueue = null;
454
455
if (__DEV__) {
456
// Also validate hook order for cascading updates.
465
} while (didScheduleRenderPhaseUpdate);
466
467
renderPhaseUpdates = null;
474
- numberOfReRenders = 0;
468
}
469
470
// We can assume the previous dispatcher is always this one, since we set it
471
// at the beginning of the render phase and there's no re-entrancy.
472
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
473
481
- const renderedWork: Fiber = (currentlyRenderingFiber: any);
482
-
483
- renderedWork.memoizedState = firstWorkInProgressHook;
484
- renderedWork.expirationTime = remainingExpirationTime;
485
- renderedWork.updateQueue = (componentUpdateQueue: any);
486
- renderedWork.effectTag |= sideEffectTag;
487
-
474
if (__DEV__) {
489
- renderedWork._debugHookTypes = hookTypesDev;
475
+ workInProgress._debugHookTypes = hookTypesDev;
476
}
477
478
// This check uses currentHook so that it works the same in DEV and prod bundles.
481
currentHook !== null && currentHook.next !== null;
482
483
renderExpirationTime = NoWork;
498
- currentlyRenderingFiber = null;
484
+ currentlyRenderingFiber = (null: any);
485
486
currentHook = null;
501
- nextCurrentHook = null;
502
- firstWorkInProgressHook = null;
487
workInProgressHook = null;
504
- nextWorkInProgressHook = null;
488
489
if (__DEV__) {
490
currentHookNameInDev = null;
492
hookTypesUpdateIndexDev = -1;
493
}
494
512
- remainingExpirationTime = NoWork;
513
- componentUpdateQueue = null;
514
- sideEffectTag = 0;
515
-
495
// These were reset above
496
// didScheduleRenderPhaseUpdate = false;
497
// renderPhaseUpdates = null;
519
- // numberOfReRenders = 0;
498
499
invariant(
500
!didRenderTooFewHooks,
527
// component is a module-style component.
528
529
renderExpirationTime = NoWork;
552
- currentlyRenderingFiber = null;
530
+ currentlyRenderingFiber = (null: any);
531
532
currentHook = null;
555
- nextCurrentHook = null;
556
- firstWorkInProgressHook = null;
533
workInProgressHook = null;
558
- nextWorkInProgressHook = null;
534
535
if (__DEV__) {
536
hookTypesDev = null;
539
currentHookNameInDev = null;
540
}
541
567
- remainingExpirationTime = NoWork;
568
- componentUpdateQueue = null;
569
- sideEffectTag = 0;
570
-
542
didScheduleRenderPhaseUpdate = false;
543
renderPhaseUpdates = null;
573
- numberOfReRenders = 0;
544
}
545
546
function mountWorkInProgressHook(): Hook {
556
557
if (workInProgressHook === null) {
558
// This is the first hook in the list
589
- firstWorkInProgressHook = workInProgressHook = hook;
559
+ currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
560
} else {
561
// Append to the end of the list
562
workInProgressHook = workInProgressHook.next = hook;
570
// clone, or a work-in-progress hook from a previous render pass that we can
571
// use as a base. When we reach the end of the base list, we must switch to
572
// the dispatcher used for mounts.
573
+ let nextCurrentHook: null | Hook;
574
+ if (currentHook === null) {
575
+ let current = currentlyRenderingFiber.alternate;
576
+ if (current !== null) {
577
+ nextCurrentHook = current.memoizedState;
578
+ } else {
579
+ nextCurrentHook = null;
580
+ }
581
+ } else {
582
+ nextCurrentHook = currentHook.next;
583
+ }
584
+
585
+ let nextWorkInProgressHook: null | Hook;
586
+ if (workInProgressHook === null) {
587
+ nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
588
+ } else {
589
+ nextWorkInProgressHook = workInProgressHook.next;
590
+ }
591
+
592
if (nextWorkInProgressHook !== null) {
593
// There's already a work-in-progress. Reuse it.
594
workInProgressHook = nextWorkInProgressHook;
595
nextWorkInProgressHook = workInProgressHook.next;
596
597
currentHook = nextCurrentHook;
609
- nextCurrentHook = currentHook !== null ? currentHook.next : null;
598
} else {
599
// Clone from the current hook.
600
+
601
invariant(
602
nextCurrentHook !== null,
603
'Rendered more hooks than during the previous render.',
616
617
if (workInProgressHook === null) {
618
// This is the first hook in the list.
630
- workInProgressHook = firstWorkInProgressHook = newHook;
619
+ currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
620
} else {
621
// Append to the end of the list.
622
workInProgressHook = workInProgressHook.next = newHook;
623
}
635
- nextCurrentHook = currentHook.next;
624
}
625
return workInProgressHook;
626
}
656
});
657
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
658
null,
671
- // Flow doesn't know this is non-null, but we do.
672
- ((currentlyRenderingFiber: any): Fiber),
659
+ currentlyRenderingFiber,
660
queue,
661
): any));
662
return [hook.memoizedState, dispatch];
676
677
queue.lastRenderedReducer = reducer;
678
692
- if (numberOfReRenders > 0) {
679
+ if (renderPhaseUpdates !== null) {
680
// This is a re-render. Apply the new render phase updates to the previous
681
// work-in-progress hook.
682
const dispatch: Dispatch<A> = (queue.dispatch: any);
696
- if (renderPhaseUpdates !== null) {
697
- // Render phase updates are stored in a map of queue -> linked list
698
- const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
699
- if (firstRenderPhaseUpdate !== undefined) {
700
- renderPhaseUpdates.delete(queue);
701
- let newState = hook.memoizedState;
702
- let update = firstRenderPhaseUpdate;
703
- do {
704
- // Process this render phase update. We don't have to check the
705
- // priority because it will always be the same as the current
706
- // render's.
707
- const action = update.action;
708
- newState = reducer(newState, action);
709
- update = update.next;
710
- } while (update !== null);
711
-
712
- // Mark that the fiber performed work, but only if the new state is
713
- // different from the current state.
714
- if (!is(newState, hook.memoizedState)) {
715
- markWorkInProgressReceivedUpdate();
716
- }
683
+ // Render phase updates are stored in a map of queue -> linked list
684
+ const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
685
+ if (firstRenderPhaseUpdate !== undefined) {
686
+ renderPhaseUpdates.delete(queue);
687
+ let newState = hook.memoizedState;
688
+ let update = firstRenderPhaseUpdate;
689
+ do {
690
+ // Process this render phase update. We don't have to check the
691
+ // priority because it will always be the same as the current
692
+ // render's.
693
+ const action = update.action;
694
+ newState = reducer(newState, action);
695
+ update = update.next;
696
+ } while (update !== null);
697
+
698
+ // Mark that the fiber performed work, but only if the new state is
699
+ // different from the current state.
700
+ if (!is(newState, hook.memoizedState)) {
701
+ markWorkInProgressReceivedUpdate();
702
+ }
703
718
- hook.memoizedState = newState;
719
- // Don't persist the state accumulated from the render phase updates to
720
- // the base state unless the queue is empty.
721
- // TODO: Not sure if this is the desired semantics, but it's what we
722
- // do for gDSFP. I can't remember why.
723
- if (hook.baseUpdate === queue.last) {
724
- hook.baseState = newState;
725
- }
704
+ hook.memoizedState = newState;
705
+ // Don't persist the state accumulated from the render phase updates to
706
+ // the base state unless the queue is empty.
707
+ // TODO: Not sure if this is the desired semantics, but it's what we
708
+ // do for gDSFP. I can't remember why.
709
+ if (hook.baseUpdate === queue.last) {
710
+ hook.baseState = newState;
711
+ }
712
727
- queue.lastRenderedState = newState;
713
+ queue.lastRenderedState = newState;
714
729
- return [newState, dispatch];
730
- }
715
+ return [newState, dispatch];
716
}
717
return [hook.memoizedState, dispatch];
718
}
755
newBaseState = newState;
756
}
757
// Update the remaining priority in the queue.
773
- if (updateExpirationTime > remainingExpirationTime) {
774
- remainingExpirationTime = updateExpirationTime;
775
- markUnprocessedUpdateTime(remainingExpirationTime);
758
+ if (updateExpirationTime > currentlyRenderingFiber.expirationTime) {
759
+ currentlyRenderingFiber.expirationTime = updateExpirationTime;
760
+ markUnprocessedUpdateTime(updateExpirationTime);
761
}
762
} else {
763
// This update does have sufficient priority.
827
BasicStateAction<S>,
828
> = (queue.dispatch = (dispatchAction.bind(
829
null,
845
- // Flow doesn't know this is non-null, but we do.
846
- ((currentlyRenderingFiber: any): Fiber),
830
+ currentlyRenderingFiber,
831
queue,
832
): any));
833
return [hook.memoizedState, dispatch];
848
// Circular
849
next: (null: any),
850
};
851
+ let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
852
if (componentUpdateQueue === null) {
853
componentUpdateQueue = createFunctionComponentUpdateQueue();
854
+ currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
855
componentUpdateQueue.lastEffect = effect.next = effect;
856
} else {
857
const lastEffect = componentUpdateQueue.lastEffect;
885
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
886
const hook = mountWorkInProgressHook();
887
const nextDeps = deps === undefined ? null : deps;
902
- sideEffectTag |= fiberEffectTag;
888
+ currentlyRenderingFiber.effectTag |= fiberEffectTag;
889
hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
890
}
891
906
}
907
}
908
923
- sideEffectTag |= fiberEffectTag;
909
+ currentlyRenderingFiber.effectTag |= fiberEffectTag;
910
+
911
hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps);
912
}
913
918
if (__DEV__) {
919
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
920
if ('undefined' !== typeof jest) {
934
- warnIfNotCurrentlyActingEffectsInDEV(
935
- ((currentlyRenderingFiber: any): Fiber),
936
- );
921
+ warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
922
}
923
}
924
return mountEffectImpl(
936
if (__DEV__) {
937
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
938
if ('undefined' !== typeof jest) {
954
- warnIfNotCurrentlyActingEffectsInDEV(
955
- ((currentlyRenderingFiber: any): Fiber),
956
- );
939
+ warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
940
}
941
}
942
return updateEffectImpl(
1205
queue: UpdateQueue<S, A>,
1206
action: A,
1207
) {
1225
- invariant(
1226
- numberOfReRenders < RE_RENDER_LIMIT,
1227
- 'Too many re-renders. React limits the number of renders to prevent ' +
1228
- 'an infinite loop.',
1229
- );
1230
-
1208
if (__DEV__) {
1209
warning(
1210
typeof arguments[3] !== 'function',