mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
DevTools: Show hook names based on variable usage (#21641)
Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com> Co-authored-by: Saphal Patro <saphal1998@gmail.com> Co-authored-by: VibhorCodecianGupta <vibhordelgupta@gmail.com>
This commit is contained in:
co-authored by
Brian Vaughn
Saphal Patro
VibhorCodecianGupta
parent
ab390c65ee
commit
c5cfa71948
@@ -26,6 +26,7 @@ export type IconType =
|
||||
| 'log-data'
|
||||
| 'more'
|
||||
| 'next'
|
||||
| 'parse-hook-names'
|
||||
| 'previous'
|
||||
| 'record'
|
||||
| 'reload'
|
||||
@@ -92,6 +93,9 @@ export default function ButtonIcon({className = '', type}: Props) {
|
||||
case 'next':
|
||||
pathData = PATH_NEXT;
|
||||
break;
|
||||
case 'parse-hook-names':
|
||||
pathData = PATH_PARSE_HOOK_NAMES;
|
||||
break;
|
||||
case 'previous':
|
||||
pathData = PATH_PREVIOUS;
|
||||
break;
|
||||
@@ -141,7 +145,11 @@ export default function ButtonIcon({className = '', type}: Props) {
|
||||
height="24"
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path fill="currentColor" d={pathData} />
|
||||
{typeof pathData === 'string' ? (
|
||||
<path fill="currentColor" d={pathData} />
|
||||
) : (
|
||||
pathData
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -197,6 +205,15 @@ const PATH_MORE = `
|
||||
|
||||
const PATH_NEXT = 'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z';
|
||||
|
||||
const PATH_PARSE_HOOK_NAMES = (
|
||||
<g>
|
||||
<polygon points="20,7 20.94,4.94 23,4 20.94,3.06 20,1 19.06,3.06 17,4 19.06,4.94" />
|
||||
<polygon points="8.5,7 9.44,4.94 11.5,4 9.44,3.06 8.5,1 7.56,3.06 5.5,4 7.56,4.94" />
|
||||
<polygon points="20,12.5 19.06,14.56 17,15.5 19.06,16.44 20,18.5 20.94,16.44 23,15.5 20.94,14.56" />
|
||||
<path d="M17.71,9.12l-2.83-2.83C14.68,6.1,14.43,6,14.17,6c-0.26,0-0.51,0.1-0.71,0.29L2.29,17.46c-0.39,0.39-0.39,1.02,0,1.41 l2.83,2.83C5.32,21.9,5.57,22,5.83,22s0.51-0.1,0.71-0.29l11.17-11.17C18.1,10.15,18.1,9.51,17.71,9.12z M14.17,8.42l1.41,1.41 L14.41,11L13,9.59L14.17,8.42z M5.83,19.59l-1.41-1.41L11.59,11L13,12.41L5.83,19.59z" />
|
||||
</g>
|
||||
);
|
||||
|
||||
const PATH_PREVIOUS =
|
||||
'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z';
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function Badge({
|
||||
type,
|
||||
children,
|
||||
}: Props) {
|
||||
if (hocDisplayNames === null) {
|
||||
if (hocDisplayNames === null || hocDisplayNames.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,4 +68,5 @@
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-sans-large);
|
||||
color: var(--color-dim);
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@@ -64,5 +64,5 @@
|
||||
padding: 0.25rem;
|
||||
color: var(--color-dimmer);
|
||||
font-style: italic;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
|
||||
+9
-1
@@ -39,7 +39,12 @@ export default function InspectedElementWrapper(_: Props) {
|
||||
const store = useContext(StoreContext);
|
||||
const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
|
||||
|
||||
const {inspectedElement} = useContext(InspectedElementContext);
|
||||
const {
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
} = useContext(InspectedElementContext);
|
||||
|
||||
const element =
|
||||
inspectedElementID !== null
|
||||
@@ -268,7 +273,10 @@ export default function InspectedElementWrapper(_: Props) {
|
||||
inspectedElementID /* Force reset when selected Element changes */
|
||||
}
|
||||
element={element}
|
||||
hookNames={hookNames}
|
||||
inspectedElement={inspectedElement}
|
||||
parseHookNames={parseHookNames}
|
||||
toggleParseHookNames={toggleParseHookNames}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+57
-1
@@ -18,13 +18,19 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {enableHookNameParsing} from 'react-devtools-feature-flags';
|
||||
import {TreeStateContext} from './TreeContext';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import {
|
||||
checkForUpdate,
|
||||
inspectElement,
|
||||
} from 'react-devtools-shared/src/inspectedElementCache';
|
||||
import {loadHookNames} from 'react-devtools-shared/src/hookNamesCache';
|
||||
import {ElementTypeFunction} from 'react-devtools-shared/src/types';
|
||||
import LoadHookNamesFunctionContext from 'react-devtools-shared/src/devtools/views/Components/LoadHookNamesFunctionContext';
|
||||
import {SettingsContext} from '../Settings/SettingsContext';
|
||||
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
import type {
|
||||
Element,
|
||||
@@ -33,10 +39,14 @@ import type {
|
||||
|
||||
type Path = Array<string | number>;
|
||||
type InspectPathFunction = (path: Path) => void;
|
||||
export type ToggleParseHookNames = () => void;
|
||||
|
||||
type Context = {|
|
||||
hookNames: HookNames | null,
|
||||
inspectedElement: InspectedElement | null,
|
||||
inspectPaths: InspectPathFunction,
|
||||
parseHookNames: boolean,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
|};
|
||||
|
||||
export const InspectedElementContext = createContext<Context>(
|
||||
@@ -51,8 +61,10 @@ export type Props = {|
|
||||
|
||||
export function InspectedElementContextController({children}: Props) {
|
||||
const {selectedElementID} = useContext(TreeStateContext);
|
||||
const loadHookNamesFunction = useContext(LoadHookNamesFunctionContext);
|
||||
const bridge = useContext(BridgeContext);
|
||||
const store = useContext(StoreContext);
|
||||
const {parseHookNames: parseHookNamesByDefault} = useContext(SettingsContext);
|
||||
|
||||
const refresh = useCacheRefresh();
|
||||
|
||||
@@ -68,6 +80,13 @@ export function InspectedElementContextController({children}: Props) {
|
||||
path: null,
|
||||
});
|
||||
|
||||
// Parse the currently inspected element's hook names.
|
||||
// This may be enabled by default (for all elements)
|
||||
// or it may be opted into on a per-element basis (if it's too slow to be on by default).
|
||||
const [parseHookNames, setParseHookNames] = useState<boolean>(
|
||||
parseHookNamesByDefault,
|
||||
);
|
||||
|
||||
const element =
|
||||
selectedElementID !== null ? store.getElementByID(selectedElementID) : null;
|
||||
|
||||
@@ -79,14 +98,41 @@ export function InspectedElementContextController({children}: Props) {
|
||||
element,
|
||||
path: null,
|
||||
});
|
||||
|
||||
setParseHookNames(parseHookNamesByDefault);
|
||||
}
|
||||
|
||||
// Don't load a stale element from the backend; it wastes bridge bandwidth.
|
||||
let inspectedElement = null;
|
||||
let hookNames: HookNames | null = null;
|
||||
if (!elementHasChanged && element !== null) {
|
||||
inspectedElement = inspectElement(element, state.path, store, bridge);
|
||||
|
||||
if (enableHookNameParsing) {
|
||||
if (parseHookNames) {
|
||||
if (
|
||||
inspectedElement !== null &&
|
||||
inspectedElement.type === ElementTypeFunction &&
|
||||
inspectedElement.hooks !== null &&
|
||||
loadHookNamesFunction !== null
|
||||
) {
|
||||
hookNames = loadHookNames(
|
||||
element,
|
||||
inspectedElement.hooks,
|
||||
loadHookNamesFunction,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleParseHookNames: ToggleParseHookNames = useCallback<ToggleParseHookNames>(() => {
|
||||
startTransition(() => {
|
||||
setParseHookNames(value => !value);
|
||||
refresh();
|
||||
});
|
||||
}, [setParseHookNames]);
|
||||
|
||||
const inspectPaths: InspectPathFunction = useCallback<InspectPathFunction>(
|
||||
(path: Path) => {
|
||||
startTransition(() => {
|
||||
@@ -125,6 +171,7 @@ export function InspectedElementContextController({children}: Props) {
|
||||
}
|
||||
}, [
|
||||
element,
|
||||
hookNames,
|
||||
// Reset this timer any time the element we're inspecting gets a new response.
|
||||
// No sense to ping right away after e.g. inspecting/hydrating a path.
|
||||
inspectedElement,
|
||||
@@ -133,10 +180,19 @@ export function InspectedElementContextController({children}: Props) {
|
||||
|
||||
const value = useMemo<Context>(
|
||||
() => ({
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
inspectPaths,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
}),
|
||||
[inspectedElement, inspectPaths],
|
||||
[
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
inspectPaths,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
.Wrapper {
|
||||
border-left: 1px solid var(--color-border);
|
||||
height: 100%;
|
||||
}
|
||||
+4
@@ -77,4 +77,8 @@
|
||||
margin-right: 0.25rem;
|
||||
border-radius: 0.125rem;
|
||||
padding: 0.125rem 0.25rem;
|
||||
}
|
||||
|
||||
.HookName {
|
||||
color: var(--color-component-name);
|
||||
}
|
||||
+66
-4
@@ -13,6 +13,7 @@ import {useCallback, useContext, useRef, useState} from 'react';
|
||||
import {BridgeContext, StoreContext} from '../context';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import ExpandCollapseToggle from './ExpandCollapseToggle';
|
||||
import KeyValue from './KeyValue';
|
||||
import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
|
||||
@@ -20,28 +21,49 @@ import Store from '../../store';
|
||||
import styles from './InspectedElementHooksTree.css';
|
||||
import useContextMenu from '../../ContextMenu/useContextMenu';
|
||||
import {meta} from '../../../hydration';
|
||||
import {enableProfilerChangedHookIndices} from 'react-devtools-feature-flags';
|
||||
import {
|
||||
enableHookNameParsing,
|
||||
enableProfilerChangedHookIndices,
|
||||
} from 'react-devtools-feature-flags';
|
||||
|
||||
import type {InspectedElement} from './types';
|
||||
import type {HooksNode, HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {Element} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
|
||||
type HooksTreeViewProps = {|
|
||||
bridge: FrontendBridge,
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
inspectedElement: InspectedElement,
|
||||
parseHookNames: boolean,
|
||||
store: Store,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
|};
|
||||
|
||||
export function InspectedElementHooksTree({
|
||||
bridge,
|
||||
element,
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
parseHookNames,
|
||||
store,
|
||||
toggleParseHookNames,
|
||||
}: HooksTreeViewProps) {
|
||||
const {hooks, id} = inspectedElement;
|
||||
|
||||
// Changing parseHookNames is done in a transition, because it suspends.
|
||||
// This value is done outside of the transition, so the UI toggle feels responsive.
|
||||
const [parseHookNamesOptimistic, setParseHookNamesOptimistic] = useState(
|
||||
parseHookNames,
|
||||
);
|
||||
const handleChange = () => {
|
||||
setParseHookNamesOptimistic(!parseHookNames);
|
||||
toggleParseHookNames();
|
||||
};
|
||||
|
||||
const handleCopy = () => copy(serializeHooksForCopy(hooks));
|
||||
|
||||
if (hooks === null) {
|
||||
@@ -51,11 +73,25 @@ export function InspectedElementHooksTree({
|
||||
<div className={styles.HooksTreeView}>
|
||||
<div className={styles.HeaderRow}>
|
||||
<div className={styles.Header}>hooks</div>
|
||||
{enableHookNameParsing && !parseHookNames && (
|
||||
<Toggle
|
||||
isChecked={parseHookNamesOptimistic}
|
||||
isDisabled={parseHookNamesOptimistic}
|
||||
onChange={handleChange}
|
||||
title={
|
||||
parseHookNames
|
||||
? 'Parse hook names'
|
||||
: 'Parse hook names (may be slow)'
|
||||
}>
|
||||
<ButtonIcon type="parse-hook-names" />
|
||||
</Toggle>
|
||||
)}
|
||||
<Button onClick={handleCopy} title="Copy to clipboard">
|
||||
<ButtonIcon type="copy" />
|
||||
</Button>
|
||||
</div>
|
||||
<InnerHooksTreeView
|
||||
hookNames={hookNames}
|
||||
hooks={hooks}
|
||||
id={id}
|
||||
element={element}
|
||||
@@ -69,6 +105,7 @@ export function InspectedElementHooksTree({
|
||||
|
||||
type InnerHooksTreeViewProps = {|
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
hooks: HooksTree,
|
||||
id: number,
|
||||
inspectedElement: InspectedElement,
|
||||
@@ -77,6 +114,7 @@ type InnerHooksTreeViewProps = {|
|
||||
|
||||
export function InnerHooksTreeView({
|
||||
element,
|
||||
hookNames,
|
||||
hooks,
|
||||
id,
|
||||
inspectedElement,
|
||||
@@ -88,6 +126,7 @@ export function InnerHooksTreeView({
|
||||
key={index}
|
||||
element={element}
|
||||
hook={hooks[index]}
|
||||
hookNames={hookNames}
|
||||
id={id}
|
||||
inspectedElement={inspectedElement}
|
||||
path={path.concat([index])}
|
||||
@@ -98,12 +137,20 @@ export function InnerHooksTreeView({
|
||||
type HookViewProps = {|
|
||||
element: Element,
|
||||
hook: HooksNode,
|
||||
hookNames: HookNames | null,
|
||||
id: number,
|
||||
inspectedElement: InspectedElement,
|
||||
path: Array<string | number>,
|
||||
|};
|
||||
|
||||
function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
function HookView({
|
||||
element,
|
||||
hook,
|
||||
hookNames,
|
||||
id,
|
||||
inspectedElement,
|
||||
path,
|
||||
}: HookViewProps) {
|
||||
const {
|
||||
canEditHooks,
|
||||
canEditHooksAndDeletePaths,
|
||||
@@ -180,6 +227,16 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
let displayValue;
|
||||
let isComplexDisplayValue = false;
|
||||
|
||||
const hookName = hookNames != null ? hookNames.get(hook) : null;
|
||||
const hookDisplayName = hookName ? (
|
||||
<>
|
||||
{name}
|
||||
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
|
||||
</>
|
||||
) : (
|
||||
name
|
||||
);
|
||||
|
||||
// Format data for display to mimic the props/state/context for now.
|
||||
if (type === 'string') {
|
||||
displayValue = `"${((value: any): string)}"`;
|
||||
@@ -204,6 +261,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
<InnerHooksTreeView
|
||||
element={element}
|
||||
hooks={subHooks}
|
||||
hookNames={hookNames}
|
||||
id={id}
|
||||
inspectedElement={inspectedElement}
|
||||
path={path.concat(['subHooks'])}
|
||||
@@ -219,6 +277,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name="subHooks"
|
||||
path={path.concat(['subHooks'])}
|
||||
@@ -236,7 +295,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
<span
|
||||
onClick={toggleIsOpen}
|
||||
className={name !== '' ? styles.Name : styles.NameAnonymous}>
|
||||
{name || 'Anonymous'}
|
||||
{hookDisplayName || 'Anonymous'}
|
||||
</span>
|
||||
<span className={styles.Value} onClick={toggleIsOpen}>
|
||||
{isOpen || getMetaValueLabel(value)}
|
||||
@@ -253,6 +312,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name="DebugValue"
|
||||
path={path.concat(['value'])}
|
||||
@@ -272,7 +332,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
<span
|
||||
onClick={toggleIsOpen}
|
||||
className={name !== '' ? styles.Name : styles.NameAnonymous}>
|
||||
{name || 'Anonymous'}
|
||||
{hookDisplayName || 'Anonymous'}
|
||||
</span>{' '}
|
||||
{/* $FlowFixMe */}
|
||||
<span className={styles.Value} onClick={toggleIsOpen}>
|
||||
@@ -299,6 +359,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name={name}
|
||||
path={path.concat(['value'])}
|
||||
@@ -320,6 +381,7 @@ function HookView({element, hook, id, inspectedElement, path}: HookViewProps) {
|
||||
depth={1}
|
||||
element={element}
|
||||
hookID={hookID}
|
||||
hookName={hookName}
|
||||
inspectedElement={inspectedElement}
|
||||
name={name}
|
||||
path={[]}
|
||||
|
||||
+11
-1
@@ -36,19 +36,26 @@ import styles from './InspectedElementView.css';
|
||||
|
||||
import type {ContextMenuContextType} from '../context';
|
||||
import type {Element, InspectedElement, SerializedElement} from './types';
|
||||
import type {ElementType} from 'react-devtools-shared/src/types';
|
||||
import type {ElementType, HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {ToggleParseHookNames} from './InspectedElementContext';
|
||||
|
||||
export type CopyPath = (path: Array<string | number>) => void;
|
||||
export type InspectPath = (path: Array<string | number>) => void;
|
||||
|
||||
type Props = {|
|
||||
element: Element,
|
||||
hookNames: HookNames | null,
|
||||
inspectedElement: InspectedElement,
|
||||
parseHookNames: boolean,
|
||||
toggleParseHookNames: ToggleParseHookNames,
|
||||
|};
|
||||
|
||||
export default function InspectedElementView({
|
||||
element,
|
||||
hookNames,
|
||||
inspectedElement,
|
||||
parseHookNames,
|
||||
toggleParseHookNames,
|
||||
}: Props) {
|
||||
const {id} = element;
|
||||
const {
|
||||
@@ -103,8 +110,11 @@ export default function InspectedElementView({
|
||||
<InspectedElementHooksTree
|
||||
bridge={bridge}
|
||||
element={element}
|
||||
hookNames={hookNames}
|
||||
inspectedElement={inspectedElement}
|
||||
parseHookNames={parseHookNames}
|
||||
store={store}
|
||||
toggleParseHookNames={toggleParseHookNames}
|
||||
/>
|
||||
|
||||
<InspectedElementContextTree
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
flex: 0 0 auto;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.EditableName {
|
||||
color: var(--color-attribute-name);
|
||||
flex: 0 0 auto;
|
||||
@@ -48,4 +49,8 @@
|
||||
.DeleteArrayItemButton {
|
||||
padding: 0;
|
||||
margin-right: 0.125rem;
|
||||
}
|
||||
|
||||
.HookName {
|
||||
color: var(--color-component-name);
|
||||
}
|
||||
@@ -43,6 +43,7 @@ type KeyValueProps = {|
|
||||
element: Element,
|
||||
hidden: boolean,
|
||||
hookID?: ?number,
|
||||
hookName?: ?string,
|
||||
inspectedElement: InspectedElement,
|
||||
isDirectChildOfAnArray?: boolean,
|
||||
name: string,
|
||||
@@ -65,6 +66,7 @@ export default function KeyValue({
|
||||
isDirectChildOfAnArray,
|
||||
hidden,
|
||||
hookID,
|
||||
hookName,
|
||||
name,
|
||||
path,
|
||||
pathRoot,
|
||||
@@ -202,7 +204,12 @@ export default function KeyValue({
|
||||
<DeleteToggle name={name} deletePath={deletePath} path={path} />
|
||||
);
|
||||
} else {
|
||||
renderedName = <span className={styles.Name}>{name}</span>;
|
||||
renderedName = (
|
||||
<span className={styles.Name}>
|
||||
{name}
|
||||
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
} else if (canRenameTheCurrentPath) {
|
||||
renderedName = (
|
||||
@@ -215,7 +222,12 @@ export default function KeyValue({
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
renderedName = <span className={styles.Name}>{name}</span>;
|
||||
renderedName = (
|
||||
<span className={styles.Name}>
|
||||
{name}
|
||||
{!!hookName && <span className={styles.HookName}>({hookName})</span>}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let children = null;
|
||||
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
// @flow
|
||||
|
||||
import {createContext} from 'react';
|
||||
import type {LoadHookNamesFunction} from '../DevTools';
|
||||
|
||||
export type Context = LoadHookNamesFunction | null;
|
||||
|
||||
const LoadHookNamesFunctionContext = createContext<Context>(null);
|
||||
LoadHookNamesFunctionContext.displayName = 'LoadHookNamesFunctionContext';
|
||||
|
||||
export default LoadHookNamesFunctionContext;
|
||||
+48
-32
@@ -22,6 +22,7 @@ import TabBar from './TabBar';
|
||||
import {SettingsContextController} from './Settings/SettingsContext';
|
||||
import {TreeContextController} from './Components/TreeContext';
|
||||
import ViewElementSourceContext from './Components/ViewElementSourceContext';
|
||||
import LoadHookNamesFunctionContext from './Components/LoadHookNamesFunctionContext';
|
||||
import {ProfilerContextController} from './Profiler/ProfilerContext';
|
||||
import {ModalDialogContextController} from './ModalDialog';
|
||||
import ReactLogo from './ReactLogo';
|
||||
@@ -34,15 +35,22 @@ import styles from './DevTools.css';
|
||||
|
||||
import './root.css';
|
||||
|
||||
import type {HooksTree} from 'react-debug-tools/src/ReactDebugHooks';
|
||||
import type {InspectedElement} from 'react-devtools-shared/src/devtools/views/Components/types';
|
||||
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
|
||||
import type {HookNames} from 'react-devtools-shared/src/types';
|
||||
import type {Thenable} from '../cache';
|
||||
|
||||
export type BrowserTheme = 'dark' | 'light';
|
||||
export type TabID = 'components' | 'profiler';
|
||||
|
||||
export type ViewElementSource = (
|
||||
id: number,
|
||||
inspectedElement: InspectedElement,
|
||||
) => void;
|
||||
export type LoadHookNamesFunction = (
|
||||
hooksTree: HooksTree,
|
||||
) => Thenable<HookNames>;
|
||||
export type ViewAttributeSource = (
|
||||
id: number,
|
||||
path: Array<string | number>,
|
||||
@@ -75,6 +83,11 @@ export type Props = {|
|
||||
// but individual tabs (e.g. Components, Profiling) can be rendered into portals within their browser panels.
|
||||
componentsPortalContainer?: Element,
|
||||
profilerPortalContainer?: Element,
|
||||
|
||||
// Loads and parses source maps for function components
|
||||
// and extracts hook "names" based on the variables the hook return values get assigned to.
|
||||
// Not every DevTools build can load source maps, so this property is optional.
|
||||
loadHookNamesFunction?: ?LoadHookNamesFunction,
|
||||
|};
|
||||
|
||||
const componentsTab = {
|
||||
@@ -99,6 +112,7 @@ export default function DevTools({
|
||||
componentsPortalContainer,
|
||||
defaultTab = 'components',
|
||||
enabledInspectedElementContextMenu = false,
|
||||
loadHookNamesFunction,
|
||||
overrideTab,
|
||||
profilerPortalContainer,
|
||||
showTabBar = false,
|
||||
@@ -180,7 +194,6 @@ export default function DevTools({
|
||||
}
|
||||
};
|
||||
}, [bridge]);
|
||||
|
||||
return (
|
||||
<BridgeContext.Provider value={bridge}>
|
||||
<StoreContext.Provider value={store}>
|
||||
@@ -191,40 +204,43 @@ export default function DevTools({
|
||||
componentsPortalContainer={componentsPortalContainer}
|
||||
profilerPortalContainer={profilerPortalContainer}>
|
||||
<ViewElementSourceContext.Provider value={viewElementSource}>
|
||||
<TreeContextController>
|
||||
<ProfilerContextController>
|
||||
<div className={styles.DevTools} ref={devToolsRef}>
|
||||
{showTabBar && (
|
||||
<div className={styles.TabBar}>
|
||||
<ReactLogo />
|
||||
<span className={styles.DevToolsVersion}>
|
||||
{process.env.DEVTOOLS_VERSION}
|
||||
</span>
|
||||
<div className={styles.Spacer} />
|
||||
<TabBar
|
||||
currentTab={tab}
|
||||
id="DevTools"
|
||||
selectTab={setTab}
|
||||
tabs={tabs}
|
||||
type="navigation"
|
||||
<LoadHookNamesFunctionContext.Provider
|
||||
value={loadHookNamesFunction || null}>
|
||||
<TreeContextController>
|
||||
<ProfilerContextController>
|
||||
<div className={styles.DevTools} ref={devToolsRef}>
|
||||
{showTabBar && (
|
||||
<div className={styles.TabBar}>
|
||||
<ReactLogo />
|
||||
<span className={styles.DevToolsVersion}>
|
||||
{process.env.DEVTOOLS_VERSION}
|
||||
</span>
|
||||
<div className={styles.Spacer} />
|
||||
<TabBar
|
||||
currentTab={tab}
|
||||
id="DevTools"
|
||||
selectTab={setTab}
|
||||
tabs={tabs}
|
||||
type="navigation"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'components'}>
|
||||
<Components
|
||||
portalContainer={componentsPortalContainer}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'components'}>
|
||||
<Components
|
||||
portalContainer={componentsPortalContainer}
|
||||
/>
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'profiler'}>
|
||||
<Profiler portalContainer={profilerPortalContainer} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={styles.TabContent}
|
||||
hidden={tab !== 'profiler'}>
|
||||
<Profiler portalContainer={profilerPortalContainer} />
|
||||
</div>
|
||||
</div>
|
||||
</ProfilerContextController>
|
||||
</TreeContextController>
|
||||
</ProfilerContextController>
|
||||
</TreeContextController>
|
||||
</LoadHookNamesFunctionContext.Provider>
|
||||
</ViewElementSourceContext.Provider>
|
||||
</SettingsContextController>
|
||||
<UnsupportedBridgeProtocolDialog />
|
||||
|
||||
+22
@@ -16,11 +16,13 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import {enableHookNameParsing} from 'react-devtools-feature-flags';
|
||||
import {useSubscription} from '../hooks';
|
||||
import {StoreContext} from '../context';
|
||||
import Button from '../Button';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
import Toggle from '../Toggle';
|
||||
import {SettingsContext} from '../Settings/SettingsContext';
|
||||
import {
|
||||
ComponentFilterDisplayName,
|
||||
ComponentFilterElementType,
|
||||
@@ -50,6 +52,7 @@ import type {
|
||||
|
||||
export default function ComponentsSettings(_: {||}) {
|
||||
const store = useContext(StoreContext);
|
||||
const {parseHookNames, setParseHookNames} = useContext(SettingsContext);
|
||||
|
||||
const collapseNodesByDefaultSubscription = useMemo(
|
||||
() => ({
|
||||
@@ -72,6 +75,13 @@ export default function ComponentsSettings(_: {||}) {
|
||||
[store],
|
||||
);
|
||||
|
||||
const updateParseHookNames = useCallback(
|
||||
({currentTarget}) => {
|
||||
setParseHookNames(currentTarget.checked);
|
||||
},
|
||||
[setParseHookNames],
|
||||
);
|
||||
|
||||
const [componentFilters, setComponentFilters] = useState<
|
||||
Array<ComponentFilter>,
|
||||
>(() => [...store.componentFilters]);
|
||||
@@ -252,6 +262,18 @@ export default function ComponentsSettings(_: {||}) {
|
||||
Expand component tree by default
|
||||
</label>
|
||||
|
||||
{enableHookNameParsing && (
|
||||
<label className={styles.Setting}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={parseHookNames}
|
||||
onChange={updateParseHookNames}
|
||||
/>{' '}
|
||||
Always parse hook names from source{' '}
|
||||
<span className={styles.Warning}>(may be slow)</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className={styles.Header}>Hide components where...</div>
|
||||
|
||||
<table className={styles.Table}>
|
||||
|
||||
+12
@@ -18,6 +18,7 @@ import {
|
||||
import {
|
||||
COMFORTABLE_LINE_HEIGHT,
|
||||
COMPACT_LINE_HEIGHT,
|
||||
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
|
||||
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
|
||||
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
|
||||
LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY,
|
||||
@@ -45,6 +46,9 @@ type Context = {|
|
||||
breakOnConsoleErrors: boolean,
|
||||
setBreakOnConsoleErrors: (value: boolean) => void,
|
||||
|
||||
parseHookNames: boolean,
|
||||
setParseHookNames: (value: boolean) => void,
|
||||
|
||||
showInlineWarningsAndErrors: boolean,
|
||||
setShowInlineWarningsAndErrors: (value: boolean) => void,
|
||||
|
||||
@@ -94,6 +98,10 @@ function SettingsContextController({
|
||||
LOCAL_STORAGE_SHOULD_BREAK_ON_CONSOLE_ERRORS,
|
||||
false,
|
||||
);
|
||||
const [parseHookNames, setParseHookNames] = useLocalStorage<boolean>(
|
||||
LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY,
|
||||
false,
|
||||
);
|
||||
const [
|
||||
showInlineWarningsAndErrors,
|
||||
setShowInlineWarningsAndErrors,
|
||||
@@ -180,9 +188,11 @@ function SettingsContextController({
|
||||
displayDensity === 'compact'
|
||||
? COMPACT_LINE_HEIGHT
|
||||
: COMFORTABLE_LINE_HEIGHT,
|
||||
parseHookNames,
|
||||
setAppendComponentStack,
|
||||
setBreakOnConsoleErrors,
|
||||
setDisplayDensity,
|
||||
setParseHookNames,
|
||||
setTheme,
|
||||
setTraceUpdatesEnabled,
|
||||
setShowInlineWarningsAndErrors,
|
||||
@@ -194,9 +204,11 @@ function SettingsContextController({
|
||||
appendComponentStack,
|
||||
breakOnConsoleErrors,
|
||||
displayDensity,
|
||||
parseHookNames,
|
||||
setAppendComponentStack,
|
||||
setBreakOnConsoleErrors,
|
||||
setDisplayDensity,
|
||||
setParseHookNames,
|
||||
setTheme,
|
||||
setTraceUpdatesEnabled,
|
||||
setShowInlineWarningsAndErrors,
|
||||
|
||||
@@ -139,3 +139,7 @@
|
||||
.ReleaseNotesLink {
|
||||
color: var(--color-button-active);
|
||||
}
|
||||
|
||||
.Warning {
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
Reference in New Issue
Block a user