Skip to content

Commit 5dcd099

Browse files
committed
Simplify context change tracking
We don't need to track old context values for modern context since those values exist on the old Fiber already. At least in all the versions that we're getting the current value. This works the same for classes and functions (or anything else that uses contexts). This technique doesn't work for legacy though but that has long been deprecated and is removed completely in 19.
1 parent 1702dab commit 5dcd099

File tree

2 files changed

+26
-161
lines changed

2 files changed

+26
-161
lines changed

packages/react-devtools-shared/src/__tests__/profilerChangeDescriptions-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ describe('Profiler change descriptions', () => {
123123
expect(commitData.changeDescriptions.get(element.id))
124124
.toMatchInlineSnapshot(`
125125
{
126-
"context": null,
126+
"context": false,
127127
"didHooksChange": false,
128128
"hooks": null,
129129
"isFirstMount": false,

packages/react-devtools-shared/src/backend/fiber/renderer.js

+25-160
Original file line numberDiff line numberDiff line change
@@ -1530,16 +1530,6 @@ export function attach(
15301530
// When a mount or update is in progress, this value tracks the root that is being operated on.
15311531
let currentRootID: number = -1;
15321532

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-
15431533
// Returns a FiberInstance if one has already been generated for the Fiber or null if one has not been generated.
15441534
// Use this method while e.g. logging to avoid over-retaining Fibers.
15451535
function getFiberInstanceUnsafe(fiber: Fiber): FiberInstance | null {
@@ -1625,7 +1615,7 @@ export function attach(
16251615
};
16261616
} else {
16271617
const data: ChangeDescription = {
1628-
context: getContextChangedKeys(nextFiber),
1618+
context: getContextChanged(prevFiber, nextFiber),
16291619
didHooksChange: false,
16301620
isFirstMount: false,
16311621
props: getChangedKeys(
@@ -1659,7 +1649,7 @@ export function attach(
16591649
nextFiber.memoizedState,
16601650
);
16611651
const data: ChangeDescription = {
1662-
context: getContextChangedKeys(nextFiber),
1652+
context: getContextChanged(prevFiber, nextFiber),
16631653
didHooksChange: indices !== null && indices.length > 0,
16641654
isFirstMount: false,
16651655
props: getChangedKeys(
@@ -1677,147 +1667,33 @@ export function attach(
16771667
}
16781668
}
16791669

1680-
function updateContextsForFiber(fiber: Fiber) {
1681-
switch (getElementTypeForFiber(fiber)) {
1682-
case ElementTypeClass:
1683-
case ElementTypeForwardRef:
1684-
case ElementTypeFunction:
1685-
case ElementTypeMemo:
1686-
if (idToContextsMap !== null) {
1687-
const id = getFiberIDThrows(fiber);
1688-
const contexts = getContextsForFiber(fiber);
1689-
if (contexts !== null) {
1690-
// $FlowFixMe[incompatible-use] found when upgrading Flow
1691-
idToContextsMap.set(id, contexts);
1692-
}
1693-
}
1694-
break;
1695-
default:
1696-
break;
1697-
}
1698-
}
1699-
1700-
// Differentiates between a null context value and no context.
1701-
const NO_CONTEXT = {};
1702-
1703-
function getContextsForFiber(fiber: Fiber): [Object, any] | null {
1704-
let legacyContext = NO_CONTEXT;
1705-
let modernContext = NO_CONTEXT;
1706-
1707-
switch (getElementTypeForFiber(fiber)) {
1708-
case ElementTypeClass:
1709-
const instance = fiber.stateNode;
1710-
if (instance != null) {
1711-
if (
1712-
instance.constructor &&
1713-
instance.constructor.contextType != null
1714-
) {
1715-
modernContext = instance.context;
1716-
} else {
1717-
legacyContext = instance.context;
1718-
if (legacyContext && Object.keys(legacyContext).length === 0) {
1719-
legacyContext = NO_CONTEXT;
1720-
}
1721-
}
1722-
}
1723-
return [legacyContext, modernContext];
1724-
case ElementTypeForwardRef:
1725-
case ElementTypeFunction:
1726-
case ElementTypeMemo:
1727-
const dependencies = fiber.dependencies;
1728-
if (dependencies && dependencies.firstContext) {
1729-
modernContext = dependencies.firstContext;
1730-
}
1731-
1732-
return [legacyContext, modernContext];
1733-
default:
1734-
return null;
1735-
}
1736-
}
1737-
1738-
// Record all contexts at the time profiling is started.
1739-
// Fibers only store the current context value,
1740-
// so we need to track them separately in order to determine changed keys.
1741-
function crawlToInitializeContextsMap(fiber: Fiber) {
1742-
const id = getFiberIDUnsafe(fiber);
1743-
1744-
// Not all Fibers in the subtree have mounted yet.
1745-
// For example, Offscreen (hidden) or Suspense (suspended) subtrees won't yet be tracked.
1746-
// We can safely skip these subtrees.
1747-
if (id !== null) {
1748-
updateContextsForFiber(fiber);
1749-
1750-
let current = fiber.child;
1751-
while (current !== null) {
1752-
crawlToInitializeContextsMap(current);
1753-
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;
17541688
}
1755-
}
1756-
}
1757-
1758-
function getContextChangedKeys(fiber: Fiber): null | boolean | Array<string> {
1759-
if (idToContextsMap !== null) {
1760-
const id = getFiberIDThrows(fiber);
1761-
// $FlowFixMe[incompatible-use] found when upgrading Flow
1762-
const prevContexts = idToContextsMap.has(id)
1763-
? // $FlowFixMe[incompatible-use] found when upgrading Flow
1764-
idToContextsMap.get(id)
1765-
: null;
1766-
const nextContexts = getContextsForFiber(fiber);
1767-
1768-
if (prevContexts == null || nextContexts == null) {
1769-
return null;
1689+
if (!is(prevContext.memoizedValue, nextContext.memoizedValue)) {
1690+
return true;
17701691
}
17711692

1772-
const [prevLegacyContext, prevModernContext] = prevContexts;
1773-
const [nextLegacyContext, nextModernContext] = nextContexts;
1774-
1775-
switch (getElementTypeForFiber(fiber)) {
1776-
case ElementTypeClass:
1777-
if (prevContexts && nextContexts) {
1778-
if (nextLegacyContext !== NO_CONTEXT) {
1779-
return getChangedKeys(prevLegacyContext, nextLegacyContext);
1780-
} else if (nextModernContext !== NO_CONTEXT) {
1781-
return prevModernContext !== nextModernContext;
1782-
}
1783-
}
1784-
break;
1785-
case ElementTypeForwardRef:
1786-
case ElementTypeFunction:
1787-
case ElementTypeMemo:
1788-
if (nextModernContext !== NO_CONTEXT) {
1789-
let prevContext = prevModernContext;
1790-
let nextContext = nextModernContext;
1791-
1792-
while (prevContext && nextContext) {
1793-
// Note this only works for versions of React that support this key (e.v. 18+)
1794-
// For older versions, there's no good way to read the current context value after render has completed.
1795-
// This is because React maintains a stack of context values during render,
1796-
// but by the time DevTools is called, render has finished and the stack is empty.
1797-
if (prevContext.context !== nextContext.context) {
1798-
// If the order of context has changed, then the later context values might have
1799-
// changed too but the main reason it rerendered was earlier. Either an earlier
1800-
// context changed value but then we would have exited already. If we end up here
1801-
// it's because a state or props change caused the order of contexts used to change.
1802-
// So the main cause is not the contexts themselves.
1803-
return false;
1804-
}
1805-
if (!is(prevContext.memoizedValue, nextContext.memoizedValue)) {
1806-
return true;
1807-
}
1808-
1809-
prevContext = prevContext.next;
1810-
nextContext = nextContext.next;
1811-
}
1812-
1813-
return false;
1814-
}
1815-
break;
1816-
default:
1817-
break;
1818-
}
1693+
prevContext = prevContext.next;
1694+
nextContext = nextContext.next;
18191695
}
1820-
return null;
1696+
return false;
18211697
}
18221698

18231699
function isHookThatCanScheduleUpdate(hookObject: any) {
@@ -3002,8 +2878,6 @@ export function attach(
30022878
metadata.changeDescriptions.set(id, changeDescription);
30032879
}
30042880
}
3005-
3006-
updateContextsForFiber(fiber);
30072881
}
30082882
}
30092883
}
@@ -5209,7 +5083,6 @@ export function attach(
52095083

52105084
let currentCommitProfilingMetadata: CommitProfilingData | null = null;
52115085
let displayNamesByRootID: DisplayNamesByRootID | null = null;
5212-
let idToContextsMap: Map<number, any> | null = null;
52135086
let initialTreeBaseDurationsMap: Map<number, Array<[number, number]>> | null =
52145087
null;
52155088
let isProfiling: boolean = false;
@@ -5356,7 +5229,6 @@ export function attach(
53565229
// (e.g. when a fiber is re-rendered or when a fiber gets removed).
53575230
displayNamesByRootID = new Map();
53585231
initialTreeBaseDurationsMap = new Map();
5359-
idToContextsMap = new Map();
53605232

53615233
hook.getFiberRoots(rendererID).forEach(root => {
53625234
const rootInstance = rootToFiberInstanceMap.get(root);
@@ -5373,13 +5245,6 @@ export function attach(
53735245
const initialTreeBaseDurations: Array<[number, number]> = [];
53745246
snapshotTreeBaseDurations(rootInstance, initialTreeBaseDurations);
53755247
(initialTreeBaseDurationsMap: any).set(rootID, initialTreeBaseDurations);
5376-
5377-
if (shouldRecordChangeDescriptions) {
5378-
// Record all contexts at the time profiling is started.
5379-
// Fibers only store the current context value,
5380-
// so we need to track them separately in order to determine changed keys.
5381-
crawlToInitializeContextsMap(root.current);
5382-
}
53835248
});
53845249

53855250
isProfiling = true;

0 commit comments

Comments
 (0)