1530
// When a mount or update is in progress, this value tracks the root that is being operated on.
1531
let currentRootID: number = -1;
1532
1533
- function getFiberIDThrows(fiber: Fiber): number {
1534
- const fiberInstance = getFiberInstanceUnsafe(fiber);
1535
- if (fiberInstance !== null) {
1536
- return fiberInstance.id;
1537
- }
1538
- throw Error(
1539
- `Could not find ID for Fiber "${getDisplayNameForFiber(fiber) || ''}"`,
1540
- );
1541
- }
1542
-
1533
// Returns a FiberInstance if one has already been generated for the Fiber or null if one has not been generated.
1534
// Use this method while e.g. logging to avoid over-retaining Fibers.
1535
function getFiberInstanceUnsafe(fiber: Fiber): FiberInstance | null {
1603
prevFiber: Fiber | null,
1604
nextFiber: Fiber,
1605
): ChangeDescription | null {
1616
- switch (getElementTypeForFiber(nextFiber)) {
1617
- case ElementTypeClass:
1618
- case ElementTypeFunction:
1619
- case ElementTypeMemo:
1620
- case ElementTypeForwardRef:
1606
+ switch (nextFiber.tag) {
1607
+ case ClassComponent:
1608
if (prevFiber === null) {
1609
return {
1610
context: null,
1615
};
1616
} else {
1617
const data: ChangeDescription = {
1631
- context: getContextChangedKeys(nextFiber),
1618
+ context: getContextChanged(prevFiber, nextFiber),
1619
didHooksChange: false,
1620
isFirstMount: false,
1621
props: getChangedKeys(
1627
nextFiber.memoizedState,
1628
),
1629
};
1643
-
1644
- // Only traverse the hooks list once, depending on what info we're returning.
1630
+ return data;
1631
+ }
1632
+ case IncompleteFunctionComponent:
1633
+ case FunctionComponent:
1634
+ case IndeterminateComponent:
1635
+ case ForwardRef:
1636
+ case MemoComponent:
1637
+ case SimpleMemoComponent:
1638
+ if (prevFiber === null) {
1639
+ return {
1640
+ context: null,
1641
+ didHooksChange: false,
1642
+ isFirstMount: true,
1643
+ props: null,
1644
+ state: null,
1645
+ };
1646
+ } else {
1647
const indices = getChangedHooksIndices(
1648
prevFiber.memoizedState,
1649
nextFiber.memoizedState,
1650
);
1649
- data.hooks = indices;
1650
- data.didHooksChange = indices !== null && indices.length > 0;
1651
-
1651
+ const data: ChangeDescription = {
1652
+ context: getContextChanged(prevFiber, nextFiber),
1653
+ didHooksChange: indices !== null && indices.length > 0,
1654
+ isFirstMount: false,
1655
+ props: getChangedKeys(
1656
+ prevFiber.memoizedProps,
1657
+ nextFiber.memoizedProps,
1658
+ ),
1659
+ state: null,
1660
+ hooks: indices,
1661
+ };
1662
+ // Only traverse the hooks list once, depending on what info we're returning.
1663
return data;
1664
}
1665
default:
1667
}
1668
}
1669
1659
- function updateContextsForFiber(fiber: Fiber) {
1660
- switch (getElementTypeForFiber(fiber)) {
1661
- case ElementTypeClass:
1662
- case ElementTypeForwardRef:
1663
- case ElementTypeFunction:
1664
- case ElementTypeMemo:
1665
- if (idToContextsMap !== null) {
1666
- const id = getFiberIDThrows(fiber);
1667
- const contexts = getContextsForFiber(fiber);
1668
- if (contexts !== null) {
1669
- // $FlowFixMe[incompatible-use] found when upgrading Flow
1670
- idToContextsMap.set(id, contexts);
1671
- }
1672
- }
1673
- break;
1674
- default:
1675
- break;
1676
- }
1677
- }
1678
-
1679
- // Differentiates between a null context value and no context.
1680
- const NO_CONTEXT = {};
1681
-
1682
- function getContextsForFiber(fiber: Fiber): [Object, any] | null {
1683
- let legacyContext = NO_CONTEXT;
1684
- let modernContext = NO_CONTEXT;
1685
-
1686
- switch (getElementTypeForFiber(fiber)) {
1687
- case ElementTypeClass:
1688
- const instance = fiber.stateNode;
1689
- if (instance != null) {
1690
- if (
1691
- instance.constructor &&
1692
- instance.constructor.contextType != null
1693
- ) {
1694
- modernContext = instance.context;
1695
- } else {
1696
- legacyContext = instance.context;
1697
- if (legacyContext && Object.keys(legacyContext).length === 0) {
1698
- legacyContext = NO_CONTEXT;
1699
- }
1700
- }
1701
- }
1702
- return [legacyContext, modernContext];
1703
- case ElementTypeForwardRef:
1704
- case ElementTypeFunction:
1705
- case ElementTypeMemo:
1706
- const dependencies = fiber.dependencies;
1707
- if (dependencies && dependencies.firstContext) {
1708
- modernContext = dependencies.firstContext;
1709
- }
1710
-
1711
- return [legacyContext, modernContext];
1712
- default:
1713
- return null;
1714
- }
1715
- }
1716
-
1717
- // Record all contexts at the time profiling is started.
1718
- // Fibers only store the current context value,
1719
- // so we need to track them separately in order to determine changed keys.
1720
- function crawlToInitializeContextsMap(fiber: Fiber) {
1721
- const id = getFiberIDUnsafe(fiber);
1722
-
1723
- // Not all Fibers in the subtree have mounted yet.
1724
- // For example, Offscreen (hidden) or Suspense (suspended) subtrees won't yet be tracked.
1725
- // We can safely skip these subtrees.
1726
- if (id !== null) {
1727
- updateContextsForFiber(fiber);
1728
-
1729
- let current = fiber.child;
1730
- while (current !== null) {
1731
- crawlToInitializeContextsMap(current);
1732
- current = current.sibling;
1670
+ function getContextChanged(prevFiber: Fiber, nextFiber: Fiber): boolean {
1671
+ let prevContext =
1672
+ prevFiber.dependencies && prevFiber.dependencies.firstContext;
1673
+ let nextContext =
1674
+ nextFiber.dependencies && nextFiber.dependencies.firstContext;
1675
+
1676
+ while (prevContext && nextContext) {
1677
+ // Note this only works for versions of React that support this key (e.v. 18+)
1678
+ // For older versions, there's no good way to read the current context value after render has completed.
1679
+ // This is because React maintains a stack of context values during render,
1680
+ // but by the time DevTools is called, render has finished and the stack is empty.
1681
+ if (prevContext.context !== nextContext.context) {
1682
+ // If the order of context has changed, then the later context values might have
1683
+ // changed too but the main reason it rerendered was earlier. Either an earlier
1684
+ // context changed value but then we would have exited already. If we end up here
1685
+ // it's because a state or props change caused the order of contexts used to change.
1686
+ // So the main cause is not the contexts themselves.
1687
+ return false;
1688
}
1734
- }
1735
- }
1736
-
1737
- function getContextChangedKeys(fiber: Fiber): null | boolean | Array<string> {
1738
- if (idToContextsMap !== null) {
1739
- const id = getFiberIDThrows(fiber);
1740
- // $FlowFixMe[incompatible-use] found when upgrading Flow
1741
- const prevContexts = idToContextsMap.has(id)
1742
- ? // $FlowFixMe[incompatible-use] found when upgrading Flow
1743
- idToContextsMap.get(id)
1744
- : null;
1745
- const nextContexts = getContextsForFiber(fiber);
1746
-
1747
- if (prevContexts == null || nextContexts == null) {
1748
- return null;
1689
+ if (!is(prevContext.memoizedValue, nextContext.memoizedValue)) {
1690
+ return true;
1691
}
1692
1751
- const [prevLegacyContext, prevModernContext] = prevContexts;
1752
- const [nextLegacyContext, nextModernContext] = nextContexts;
1753
-
1754
- switch (getElementTypeForFiber(fiber)) {
1755
- case ElementTypeClass:
1756
- if (prevContexts && nextContexts) {
1757
- if (nextLegacyContext !== NO_CONTEXT) {
1758
- return getChangedKeys(prevLegacyContext, nextLegacyContext);
1759
- } else if (nextModernContext !== NO_CONTEXT) {
1760
- return prevModernContext !== nextModernContext;
1761
- }
1762
- }
1763
- break;
1764
- case ElementTypeForwardRef:
1765
- case ElementTypeFunction:
1766
- case ElementTypeMemo:
1767
- if (nextModernContext !== NO_CONTEXT) {
1768
- let prevContext = prevModernContext;
1769
- let nextContext = nextModernContext;
1770
-
1771
- while (prevContext && nextContext) {
1772
- // Note this only works for versions of React that support this key (e.v. 18+)
1773
- // For older versions, there's no good way to read the current context value after render has completed.
1774
- // This is because React maintains a stack of context values during render,
1775
- // but by the time DevTools is called, render has finished and the stack is empty.
1776
- if (prevContext.context !== nextContext.context) {
1777
- // If the order of context has changed, then the later context values might have
1778
- // changed too but the main reason it rerendered was earlier. Either an earlier
1779
- // context changed value but then we would have exited already. If we end up here
1780
- // it's because a state or props change caused the order of contexts used to change.
1781
- // So the main cause is not the contexts themselves.
1782
- return false;
1783
- }
1784
- if (!is(prevContext.memoizedValue, nextContext.memoizedValue)) {
1785
- return true;
1786
- }
1787
-
1788
- prevContext = prevContext.next;
1789
- nextContext = nextContext.next;
1790
- }
1791
-
1792
- return false;
1793
- }
1794
- break;
1795
- default:
1796
- break;
1797
- }
1693
+ prevContext = prevContext.next;
1694
+ nextContext = nextContext.next;
1695
}
1799
- return null;
1696
+ return false;
1697
}
1698
1699
function isHookThatCanScheduleUpdate(hookObject: any) {
1738
1739
const indices = [];
1740
let index = 0;
1844
- if (
1845
- next.hasOwnProperty('baseState') &&
1846
- next.hasOwnProperty('memoizedState') &&
1847
- next.hasOwnProperty('next') &&
1848
- next.hasOwnProperty('queue')
1849
- ) {
1850
- while (next !== null) {
1851
- if (didStatefulHookChange(prev, next)) {
1852
- indices.push(index);
1853
- }
1854
- next = next.next;
1855
- prev = prev.next;
1856
- index++;
1741
+ while (next !== null) {
1742
+ if (didStatefulHookChange(prev, next)) {
1743
+ indices.push(index);
1744
}
1745
+ next = next.next;
1746
+ prev = prev.next;
1747
+ index++;
1748
}
1749
1750
return indices;
1755
return null;
1756
}
1757
1868
- // We can't report anything meaningful for hooks changes.
1869
- if (
1870
- next.hasOwnProperty('baseState') &&
1871
- next.hasOwnProperty('memoizedState') &&
1872
- next.hasOwnProperty('next') &&
1873
- next.hasOwnProperty('queue')
1874
- ) {
1875
- return null;
1876
- }
1877
-
1758
const keys = new Set([...Object.keys(prev), ...Object.keys(next)]);
1759
const changedKeys = [];
1760
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
2878
metadata.changeDescriptions.set(id, changeDescription);
2879
}
2880
}
3001
-
3002
- updateContextsForFiber(fiber);
2881
}
2882
}
2883
}
5083
5084
let currentCommitProfilingMetadata: CommitProfilingData | null = null;
5085
let displayNamesByRootID: DisplayNamesByRootID | null = null;
5208
- let idToContextsMap: Map<number, any> | null = null;
5086
let initialTreeBaseDurationsMap: Map<number, Array<[number, number]>> | null =
5087
null;
5088
let isProfiling: boolean = false;
5229
// (e.g. when a fiber is re-rendered or when a fiber gets removed).
5230
displayNamesByRootID = new Map();
5231
initialTreeBaseDurationsMap = new Map();
5355
- idToContextsMap = new Map();
5232
5233
hook.getFiberRoots(rendererID).forEach(root => {
5234
const rootInstance = rootToFiberInstanceMap.get(root);
5245
const initialTreeBaseDurations: Array<[number, number]> = [];
5246
snapshotTreeBaseDurations(rootInstance, initialTreeBaseDurations);
5247
(initialTreeBaseDurationsMap: any).set(rootID, initialTreeBaseDurations);
5372
-
5373
- if (shouldRecordChangeDescriptions) {
5374
- // Record all contexts at the time profiling is started.
5375
- // Fibers only store the current context value,
5376
- // so we need to track them separately in order to determine changed keys.
5377
- crawlToInitializeContextsMap(root.current);
5378
- }
5248
});
5249
5250
isProfiling = true;