forked from facebook/react
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ReactFiberSuspenseComponent.js
127 lines (121 loc) · 4.19 KB
/
ReactFiberSuspenseComponent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Fiber} from './ReactFiber';
import type {SuspenseInstance} from './ReactFiberHostConfig';
import {SuspenseComponent, SuspenseListComponent} from 'shared/ReactWorkTags';
import {NoEffect, DidCapture} from 'shared/ReactSideEffectTags';
import {
isSuspenseInstancePending,
isSuspenseInstanceFallback,
} from './ReactFiberHostConfig';
// A null SuspenseState represents an unsuspended normal Suspense boundary.
// A non-null SuspenseState means that it is blocked for one reason or another.
// - A non-null dehydrated field means it's blocked pending hydration.
// - A non-null dehydrated field can use isSuspenseInstancePending or
// isSuspenseInstanceFallback to query the reason for being dehydrated.
// - A null dehydrated field means it's blocked by something suspending and
// we're currently showing a fallback instead.
export type SuspenseState = {|
// If this boundary is still dehydrated, we store the SuspenseInstance
// here to indicate that it is dehydrated (flag) and for quick access
// to check things like isSuspenseInstancePending.
dehydrated: null | SuspenseInstance,
|};
export type SuspenseListTailMode = 'collapsed' | 'hidden' | void;
export type SuspenseListRenderState = {|
isBackwards: boolean,
// The currently rendering tail row.
rendering: null | Fiber,
// The last of the already rendered children.
last: null | Fiber,
// Remaining rows on the tail of the list.
tail: null | Fiber,
// The absolute time in ms that we'll expire the tail rendering.
tailExpiration: number,
// Tail insertions setting.
tailMode: SuspenseListTailMode,
// Last Effect before we rendered the "rendering" item.
// Used to remove new effects added by the rendered item.
lastEffect: null | Fiber,
|};
export function shouldCaptureSuspense(
workInProgress: Fiber,
hasInvisibleParent: boolean,
): boolean {
// If it was the primary children that just suspended, capture and render the
// fallback. Otherwise, don't capture and bubble to the next boundary.
const nextState: SuspenseState | null = workInProgress.memoizedState;
if (nextState !== null) {
if (nextState.dehydrated !== null) {
// A dehydrated boundary always captures.
return true;
}
return false;
}
const props = workInProgress.memoizedProps;
// In order to capture, the Suspense component must have a fallback prop.
if (props.fallback === undefined) {
return false;
}
// Regular boundaries always capture.
if (props.unstable_avoidThisFallback !== true) {
return true;
}
// If it's a boundary we should avoid, then we prefer to bubble up to the
// parent boundary if it is currently invisible.
if (hasInvisibleParent) {
return false;
}
// If the parent is not able to handle it, we must handle it.
return true;
}
export function findFirstSuspended(row: Fiber): null | Fiber {
let node = row;
while (node !== null) {
if (node.tag === SuspenseComponent) {
const state: SuspenseState | null = node.memoizedState;
if (state !== null) {
const dehydrated: null | SuspenseInstance = state.dehydrated;
if (
dehydrated === null ||
isSuspenseInstancePending(dehydrated) ||
isSuspenseInstanceFallback(dehydrated)
) {
return node;
}
}
} else if (
node.tag === SuspenseListComponent &&
// revealOrder undefined can't be trusted because it don't
// keep track of whether it suspended or not.
node.memoizedProps.revealOrder !== undefined
) {
let didSuspend = (node.effectTag & DidCapture) !== NoEffect;
if (didSuspend) {
return node;
}
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === row) {
return null;
}
while (node.sibling === null) {
if (node.return === null || node.return === row) {
return null;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
return null;
}