Add Flow types for Fiber
Fixes https://github.com/bvaughn/react-devtools-experimental/issues/165
Ivan Babak committed
Apr 26, 2019 at 05:34 UTC
9f06bc345f78922bbe6f428280311b39a305e324
3 files changed
+160
-35
.flowconfig
+1
@@ -26,6 +26,7 @@ shells/dev/build/*
26
27
[options]
28
esproposal.class_instance_fields=enable
29
+esproposal.optional_chaining=enable
30
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
31
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
32
suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore
src/backend/renderer.js
+77
-33
@@ -573,7 +573,6 @@ export function attach(
573
}
574
}
575
576
- // eslint-disable-next-line no-unused-vars
576
function haveProfilerTimesChanged(
577
prevFiber: Fiber,
578
nextFiber: Fiber
@@ -715,7 +714,7 @@ export function attach(
714
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
715
if (isProfilingSupported) {
716
idToRootMap.set(id, currentRootID);
718
- idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration);
717
+ idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration || 0);
718
}
719
720
const hasOwnerMetadata = fiber.hasOwnProperty('_debugOwner');
@@ -732,7 +731,9 @@ export function attach(
731
732
const ownerID =
733
_debugOwner != null ? getFiberID(getPrimaryFiber(_debugOwner)) : 0;
735
- const parentID = getFiberID(getPrimaryFiber(parentFiber));
734
+ const parentID = parentFiber
735
+ ? getFiberID(getPrimaryFiber(parentFiber))
736
+ : 0;
737
738
let displayNameStringID = getStringID(displayName);
739
let keyStringID = getStringID(key);
@@ -748,14 +749,14 @@ export function attach(
749
if (isProfiling) {
750
// Tree base duration updates are included in the operations typed array.
751
// So we have to convert them from milliseconds to microseconds so we can send them as ints.
751
- const treeBaseDuration = Math.floor(fiber.treeBaseDuration * 1000);
752
+ const treeBaseDuration = Math.floor((fiber.treeBaseDuration || 0) * 1000);
753
754
pushOperation(TREE_OPERATION_UPDATE_TREE_BASE_DURATION);
755
pushOperation(id);
756
pushOperation(treeBaseDuration);
757
758
const { actualDuration } = fiber;
758
- if (actualDuration > 0) {
759
+ if (actualDuration != null && actualDuration > 0) {
760
// If profiling is active, store durations for elements that were rendered during the commit.
761
const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
762
metadata.actualDurations.push(id, actualDuration);
@@ -849,8 +850,12 @@ export function attach(
850
// get the fallback child from the inner fragment and mount
851
// it as if it was our own child. Updates handle this too.
852
const primaryChildFragment = fiber.child;
852
- const fallbackChildFragment = primaryChildFragment.sibling;
853
- const fallbackChild = fallbackChildFragment.child;
853
+ const fallbackChildFragment = primaryChildFragment
854
+ ? primaryChildFragment.sibling
855
+ : null;
856
+ const fallbackChild = fallbackChildFragment
857
+ ? fallbackChildFragment.child
858
+ : null;
859
if (fallbackChild !== null) {
860
mountFiberRecursively(
861
fallbackChild,
@@ -893,9 +898,11 @@ export function attach(
898
if (isTimedOutSuspense) {
899
// If it's showing fallback tree, let's traverse it instead.
900
const primaryChildFragment = fiber.child;
896
- const fallbackChildFragment = primaryChildFragment.sibling;
901
+ const fallbackChildFragment = primaryChildFragment
902
+ ? primaryChildFragment.sibling
903
+ : null;
904
// Skip over to the real Fiber child.
898
- child = fallbackChildFragment.child;
905
+ child = fallbackChildFragment ? fallbackChildFragment.child : null;
906
}
907
908
while (child !== null) {
@@ -913,20 +920,27 @@ export function attach(
920
const id = getFiberID(getPrimaryFiber(fiber));
921
const { actualDuration, treeBaseDuration } = fiber;
922
916
- idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration);
923
+ idToTreeBaseDurationMap.set(id, fiber.treeBaseDuration || 0);
924
925
if (isProfiling) {
919
- if (treeBaseDuration !== fiber.alternate.treeBaseDuration) {
926
+ const { alternate } = fiber;
927
+
928
+ if (
929
+ treeBaseDuration !==
930
+ (alternate ? alternate.treeBaseDuration : undefined)
931
+ ) {
932
// Tree base duration updates are included in the operations typed array.
933
// So we have to convert them from milliseconds to microseconds so we can send them as ints.
922
- const treeBaseDuration = Math.floor(fiber.treeBaseDuration * 1000);
934
+ const treeBaseDuration = Math.floor(
935
+ (fiber.treeBaseDuration || 0) * 1000
936
+ );
937
pushOperation(TREE_OPERATION_UPDATE_TREE_BASE_DURATION);
938
pushOperation(getFiberID(getPrimaryFiber(fiber)));
939
pushOperation(treeBaseDuration);
940
}
941
928
- if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
929
- if (actualDuration > 0) {
942
+ if (alternate ? haveProfilerTimesChanged(alternate, fiber) : true) {
943
+ if (actualDuration != null && actualDuration > 0) {
944
// If profiling is active, store durations for elements that were rendered during the commit.
945
const metadata = ((currentCommitProfilingMetadata: any): CommitProfilingData);
946
metadata.actualDurations.push(id, actualDuration);
@@ -1008,11 +1022,19 @@ export function attach(
1022
if (prevDidTimeout && nextDidTimeOut) {
1023
// Fallback -> Fallback:
1024
// 1. Reconcile fallback set.
1011
- const nextFallbackChildSet = nextFiber.child.sibling;
1025
+ const nextFiberChild = nextFiber.child;
1026
+ const nextFallbackChildSet = nextFiberChild
1027
+ ? nextFiberChild.sibling
1028
+ : null;
1029
// Note: We can't use nextFiber.child.sibling.alternate
1030
// because the set is special and alternate may not exist.
1014
- const prevFallbackChildSet = prevFiber.child.sibling;
1031
+ const prevFiberChild = prevFiber.child;
1032
+ const prevFallbackChildSet = prevFiberChild
1033
+ ? prevFiberChild.sibling
1034
+ : null;
1035
if (
1036
+ nextFallbackChildSet != null &&
1037
+ prevFallbackChildSet != null &&
1038
updateFiberRecursively(
1039
nextFallbackChildSet,
1040
prevFallbackChildSet,
@@ -1038,9 +1060,14 @@ export function attach(
1060
// We need to manually walk the previous tree and record unmounts.
1061
unmountFiberChildrenRecursively(prevFiber);
1062
// 2. Mount fallback set
1041
- const nextFallbackChildSet = nextFiber.child.sibling;
1042
- mountFiberRecursively(nextFallbackChildSet, nextFiber, true);
1043
- shouldResetChildren = true;
1063
+ const nextFiberChild = nextFiber.child;
1064
+ const nextFallbackChildSet = nextFiberChild
1065
+ ? nextFiberChild.sibling
1066
+ : null;
1067
+ if (nextFallbackChildSet != null) {
1068
+ mountFiberRecursively(nextFallbackChildSet, nextFiber, true);
1069
+ shouldResetChildren = true;
1070
+ }
1071
} else {
1072
// Common case: Primary -> Primary.
1073
// This is the same codepath as for non-Suspense fibers.
@@ -1109,9 +1136,12 @@ export function attach(
1136
let nextChildSet = nextFiber.child;
1137
if (nextDidTimeOut) {
1138
// Special case: timed-out Suspense renders the fallback set.
1112
- nextChildSet = nextFiber.child.sibling;
1139
+ const nextFiberChild = nextFiber.child;
1140
+ nextChildSet = nextFiberChild ? nextFiberChild.sibling : null;
1141
+ }
1142
+ if (nextChildSet != null) {
1143
+ recordResetChildren(nextFiber, nextChildSet);
1144
}
1114
- recordResetChildren(nextFiber, nextChildSet);
1145
// We've handled the child order change for this Fiber.
1146
// Since it's included, there's no need to invalidate parent child order.
1147
return false;
@@ -1260,7 +1290,7 @@ export function attach(
1290
1291
function findNativeByFiberID(id: number) {
1292
try {
1263
- const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
1293
+ const fiber = findCurrentFiberUsingSlowPathById(id);
1294
if (fiber === null) {
1295
return null;
1296
}
@@ -1550,6 +1580,11 @@ export function attach(
1580
// Find the currently mounted version of this fiber (so we don't show the wrong props and state).
1581
fiber = findCurrentFiberUsingSlowPath(fiber);
1582
1583
+ if (fiber == null) {
1584
+ console.warn(`Could not find Fiber with id "${id}"`);
1585
+ return null;
1586
+ }
1587
+
1588
const {
1589
_debugOwner,
1590
_debugSource,
@@ -1558,7 +1593,7 @@ export function attach(
1593
memoizedState,
1594
tag,
1595
type,
1561
- } = ((fiber: any): Fiber);
1596
+ } = fiber;
1597
1598
const usesHooks =
1599
(tag === FunctionComponent ||
@@ -1632,7 +1667,7 @@ export function attach(
1667
displayName: getDataForFiber(owner).displayName || 'Unknown',
1668
id: getFiberID(getPrimaryFiber(owner)),
1669
});
1635
- owner = owner._debugOwner;
1670
+ owner = owner._debugOwner || null;
1671
}
1672
}
1673
@@ -1729,13 +1764,21 @@ export function attach(
1764
}
1765
}
1766
1767
+ function findCurrentFiberUsingSlowPathById(id: number): Fiber | null {
1768
+ const fiber = idToFiberMap.get(id);
1769
+ if (fiber) {
1770
+ return findCurrentFiberUsingSlowPath(fiber);
1771
+ }
1772
+ return null;
1773
+ }
1774
+
1775
function setInHook(
1776
id: number,
1777
index: number,
1778
path: Array<string | number>,
1779
value: any
1780
) {
1738
- const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
1781
+ const fiber = findCurrentFiberUsingSlowPathById(id);
1782
if (fiber !== null) {
1783
if (typeof overrideHookState === 'function') {
1784
overrideHookState(fiber, index, path, value);
@@ -1744,7 +1787,7 @@ export function attach(
1787
}
1788
1789
function setInProps(id: number, path: Array<string | number>, value: any) {
1747
- const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
1790
+ const fiber = findCurrentFiberUsingSlowPathById(id);
1791
if (fiber !== null) {
1792
const instance = fiber.stateNode;
1793
if (instance === null) {
@@ -1759,7 +1802,7 @@ export function attach(
1802
}
1803
1804
function setInState(id: number, path: Array<string | number>, value: any) {
1762
- const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
1805
+ const fiber = findCurrentFiberUsingSlowPathById(id);
1806
if (fiber !== null) {
1807
const instance = fiber.stateNode;
1808
setInObject(instance.state, path, value);
@@ -1773,7 +1816,7 @@ export function attach(
1816
// We need to remove the first part of the path (the "value") before continuing.
1817
path = path.slice(1);
1818
1776
- const fiber = findCurrentFiberUsingSlowPath(idToFiberMap.get(id));
1819
+ const fiber = findCurrentFiberUsingSlowPathById(id);
1820
if (fiber !== null) {
1821
const instance = fiber.stateNode;
1822
if (path.length === 0) {
@@ -1940,17 +1983,18 @@ export function attach(
1983
}
1984
1985
const initialTreeBaseDurations = [];
1943
- ((initialTreeBaseDurationsMap: any): Map<number, number>).forEach(
1944
- (treeBaseDuration, id) => {
1986
+ if (initialTreeBaseDurationsMap != null) {
1987
+ initialTreeBaseDurationsMap.forEach((treeBaseDuration, id) => {
1988
if (
1946
- ((initialIDToRootMap: any): Map<number, number>).get(id) === rootID
1989
+ initialIDToRootMap != null &&
1990
+ initialIDToRootMap.get(id) === rootID
1991
) {
1992
// We don't need to convert milliseconds to microseconds in this case,
1993
// because the profiling summary is JSON serialized.
1994
initialTreeBaseDurations.push(id, treeBaseDuration);
1995
}
1952
- }
1953
- );
1996
+ });
1997
+ }
1998
1999
return {
2000
commitDurations,
src/backend/types.js
+82
-2
@@ -7,8 +7,88 @@ type BundleType =
7
| 0 // PROD
8
| 1; // DEV
9
10
-// TODO: Better type for Fiber
11
-export type Fiber = Object;
10
+export type WorkTag = number;
11
+export type TypeOfMode = number;
12
+export type SideEffectTag = number;
13
+export type ExpirationTime = number;
14
+export type RefObject = {|
15
+ current: any,
16
+|};
17
+export type Source = {
18
+ fileName: string,
19
+ lineNumber: number,
20
+};
21
+export type HookType =
22
+ | 'useState'
23
+ | 'useReducer'
24
+ | 'useContext'
25
+ | 'useRef'
26
+ | 'useEffect'
27
+ | 'useLayoutEffect'
28
+ | 'useCallback'
29
+ | 'useMemo'
30
+ | 'useImperativeHandle'
31
+ | 'useDebugValue';
32
+
33
+// Copied modified from https://github.com/facebook/react/blob/v16.9.0-alpha.0/packages/react-reconciler/src/ReactFiber.js
34
+export type Fiber = {|
35
+ tag: WorkTag,
36
+
37
+ key: null | string,
38
+
39
+ elementType: any,
40
+
41
+ type: any,
42
+
43
+ stateNode: any,
44
+
45
+ return: Fiber | null,
46
+
47
+ child: Fiber | null,
48
+ sibling: Fiber | null,
49
+ index: number,
50
+
51
+ ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,
52
+
53
+ pendingProps: any, // This type will be more specific once we overload the tag.
54
+ memoizedProps: any, // The props used to create the output.
55
+
56
+ // updateQueue: UpdateQueue<any> | null,
57
+
58
+ memoizedState: any,
59
+
60
+ // contextDependencies: ContextDependencyList | null,
61
+
62
+ mode: TypeOfMode,
63
+
64
+ effectTag: SideEffectTag,
65
+
66
+ nextEffect: Fiber | null,
67
+
68
+ firstEffect: Fiber | null,
69
+ lastEffect: Fiber | null,
70
+
71
+ expirationTime: ExpirationTime,
72
+
73
+ childExpirationTime: ExpirationTime,
74
+
75
+ alternate: Fiber | null,
76
+
77
+ actualDuration?: number,
78
+
79
+ actualStartTime?: number,
80
+
81
+ selfBaseDuration?: number,
82
+
83
+ treeBaseDuration?: number,
84
+
85
+ _debugID?: number,
86
+ _debugSource?: Source | null,
87
+ _debugOwner?: Fiber | null,
88
+ _debugIsCurrentlyTiming?: boolean,
89
+
90
+ _debugHookTypes?: Array<HookType> | null,
91
+|};
92
93
// TODO: If it's useful for the frontend to know which types of data an Element has
94
// (e.g. props, state, context, hooks) then we could add a bitmask field for this