mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Cache inspected element data until it is updated
This commit is contained in:
@@ -51,6 +51,58 @@ exports[`InspectedElementContext should inspect the currently selected element:
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should not re-render a function with hooks if it did not update since it was last inspected: 1: mount 1`] = `
|
||||
[root]
|
||||
▾ <Wrapper>
|
||||
<Anonymous>
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should not re-render a function with hooks if it did not update since it was last inspected: 2: initial render 1`] = `
|
||||
{
|
||||
"id": 3,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"events": null,
|
||||
"hooks": [
|
||||
{
|
||||
"id": 0,
|
||||
"isStateEditable": true,
|
||||
"name": "State",
|
||||
"value": 0,
|
||||
"subHooks": []
|
||||
}
|
||||
],
|
||||
"props": {
|
||||
"foo": 1,
|
||||
"bar": "abc"
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should not re-render a function with hooks if it did not update since it was last inspected: 3: updated state 1`] = `
|
||||
{
|
||||
"id": 3,
|
||||
"owners": null,
|
||||
"context": null,
|
||||
"events": null,
|
||||
"hooks": [
|
||||
{
|
||||
"id": 0,
|
||||
"isStateEditable": true,
|
||||
"name": "State",
|
||||
"value": 0,
|
||||
"subHooks": []
|
||||
}
|
||||
],
|
||||
"props": {
|
||||
"foo": 2,
|
||||
"bar": "def"
|
||||
},
|
||||
"state": null
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`InspectedElementContext should poll for updates for the currently selected element: 1: mount 1`] = `
|
||||
[root]
|
||||
<Example>
|
||||
|
||||
@@ -159,4 +159,94 @@ describe('InspectedElementContext', () => {
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should not re-render a function with hooks if it did not update since it was last inspected', async done => {
|
||||
let targetRenderCount = 0;
|
||||
|
||||
const Wrapper = ({ children }) => children;
|
||||
const Target = React.memo(props => {
|
||||
targetRenderCount++;
|
||||
React.useState(0);
|
||||
return null;
|
||||
});
|
||||
|
||||
const container = document.createElement('div');
|
||||
utils.act(() =>
|
||||
ReactDOM.render(
|
||||
<Wrapper>
|
||||
<Target foo={1} bar="abc" />
|
||||
</Wrapper>,
|
||||
container
|
||||
)
|
||||
);
|
||||
expect(store).toMatchSnapshot('1: mount');
|
||||
|
||||
const id = ((store.getElementIDAtIndex(1): any): number);
|
||||
|
||||
let inspectedElement = null;
|
||||
|
||||
function Suspender({ target }) {
|
||||
const { read } = React.useContext(InspectedElementContext);
|
||||
inspectedElement = read(target);
|
||||
return null;
|
||||
}
|
||||
|
||||
targetRenderCount = 0;
|
||||
|
||||
let renderer;
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
(renderer = TestRenderer.create(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={1}
|
||||
>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
</Contexts>
|
||||
)),
|
||||
3
|
||||
);
|
||||
expect(targetRenderCount).toBe(1);
|
||||
expect(inspectedElement).toMatchSnapshot('2: initial render');
|
||||
|
||||
const initialInspectedElement = inspectedElement;
|
||||
|
||||
targetRenderCount = 0;
|
||||
inspectedElement = null;
|
||||
await utils.actAsync(
|
||||
() =>
|
||||
renderer.update(
|
||||
<Contexts
|
||||
defaultSelectedElementID={id}
|
||||
defaultSelectedElementIndex={1}
|
||||
>
|
||||
<React.Suspense fallback={null}>
|
||||
<Suspender target={id} />
|
||||
</React.Suspense>
|
||||
</Contexts>
|
||||
),
|
||||
1
|
||||
);
|
||||
expect(targetRenderCount).toBe(0);
|
||||
expect(inspectedElement).toEqual(initialInspectedElement);
|
||||
|
||||
targetRenderCount = 0;
|
||||
|
||||
await utils.actAsync(() =>
|
||||
ReactDOM.render(
|
||||
<Wrapper>
|
||||
<Target foo={2} bar="def" />
|
||||
</Wrapper>,
|
||||
container
|
||||
)
|
||||
);
|
||||
|
||||
// Target should have been rendered once (by ReactDOM) and once by DevTools for inspection.
|
||||
expect(targetRenderCount).toBe(2);
|
||||
expect(inspectedElement).toMatchSnapshot('3: updated state');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
+45
-9
@@ -1102,6 +1102,19 @@ export function attach(
|
||||
if (__DEBUG__) {
|
||||
debug('updateFiberRecursively()', nextFiber, parentFiber);
|
||||
}
|
||||
|
||||
if (
|
||||
mostRecentlyInspectedElement !== null &&
|
||||
mostRecentlyInspectedElementID ===
|
||||
getFiberID(getPrimaryFiber(nextFiber)) &&
|
||||
hasDataChanged(prevFiber, nextFiber)
|
||||
) {
|
||||
// If this Fiber has updated, clear cached inspected data.
|
||||
// If it is inspected again, it may need to be re-run to obtain updated hooks values.
|
||||
mostRecentlyInspectedElement = null;
|
||||
mostRecentlyInspectedElementID = null;
|
||||
}
|
||||
|
||||
const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
|
||||
const isSuspense = nextFiber.tag === SuspenseComponent;
|
||||
let shouldResetChildren = false;
|
||||
@@ -1890,18 +1903,41 @@ export function attach(
|
||||
};
|
||||
}
|
||||
|
||||
let mostRecentlyInspectedElementID: number | null = null;
|
||||
let mostRecentlyInspectedElement: InspectedElement | null = null;
|
||||
|
||||
function inspectElement(id: number): InspectedElement | null {
|
||||
let result = inspectElementRaw(id);
|
||||
if (result === null) {
|
||||
// If this element has not been updated since it was last inspected, reuse the last value.
|
||||
// This avoids re-invoking a function component with hooks.
|
||||
// TODO We could send a special signal (e.g. true) to avoid serialization too.
|
||||
if (
|
||||
mostRecentlyInspectedElement !== null &&
|
||||
mostRecentlyInspectedElementID === id
|
||||
) {
|
||||
return mostRecentlyInspectedElement;
|
||||
}
|
||||
|
||||
mostRecentlyInspectedElementID = id;
|
||||
mostRecentlyInspectedElement = inspectElementRaw(id);
|
||||
if (mostRecentlyInspectedElement === null) {
|
||||
return null;
|
||||
}
|
||||
// TODO Review sanitization approach for the below inspectable values.
|
||||
result.context = cleanForBridge(result.context);
|
||||
result.events = cleanForBridge(result.events);
|
||||
result.hooks = cleanForBridge(result.hooks);
|
||||
result.props = cleanForBridge(result.props);
|
||||
result.state = cleanForBridge(result.state);
|
||||
return result;
|
||||
mostRecentlyInspectedElement.context = cleanForBridge(
|
||||
mostRecentlyInspectedElement.context
|
||||
);
|
||||
mostRecentlyInspectedElement.events = cleanForBridge(
|
||||
mostRecentlyInspectedElement.events
|
||||
);
|
||||
mostRecentlyInspectedElement.hooks = cleanForBridge(
|
||||
mostRecentlyInspectedElement.hooks
|
||||
);
|
||||
mostRecentlyInspectedElement.props = cleanForBridge(
|
||||
mostRecentlyInspectedElement.props
|
||||
);
|
||||
mostRecentlyInspectedElement.state = cleanForBridge(
|
||||
mostRecentlyInspectedElement.state
|
||||
);
|
||||
return mostRecentlyInspectedElement;
|
||||
}
|
||||
|
||||
function logElementToConsole(id) {
|
||||
|
||||
Reference in New Issue
Block a user