@samitouri / QOS-React / commits / 9f06bc345f

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