From 565489758b9b12c69560298e7b57c2a2008cd21b Mon Sep 17 00:00:00 2001 From: moklick Date: Thu, 8 Jun 2023 12:26:38 +0200 Subject: [PATCH] refactor(react): improve performance by fixing minimap --- .../additional-components/MiniMap/MiniMap.tsx | 66 +++++-------------- .../MiniMap/MiniMapNode.tsx | 11 ++-- .../MiniMap/MiniMapNodes.tsx | 65 ++++++++++++++++++ .../additional-components/MiniMap/types.ts | 11 +++- 4 files changed, 96 insertions(+), 57 deletions(-) create mode 100644 packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index 464cf5e6..b0fc80ff 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -1,25 +1,16 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, useEffect, useRef, type MouseEvent } from 'react'; +import { memo, useEffect, useRef, type MouseEvent, useCallback } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; -import { - getRectOfNodes, - getBoundsOfRects, - getNodePositionWithOrigin, - XYMinimap, - type Rect, - type XYMinimapInstance, -} from '@xyflow/system'; +import { getRectOfNodes, getBoundsOfRects, XYMinimap, type Rect, type XYMinimapInstance } from '@xyflow/system'; import { useStore, useStoreApi } from '../../hooks/useStore'; import Panel from '../../components/Panel'; import type { ReactFlowState } from '../../types'; -import MiniMapNode from './MiniMapNode'; -import type { MiniMapProps, GetMiniMapNodeAttribute } from './types'; - -declare const window: any; +import type { MiniMapProps } from './types'; +import MiniMapNodes from './MiniMapNodes'; const defaultWidth = 200; const defaultHeight = 150; @@ -34,7 +25,6 @@ const selector = (s: ReactFlowState) => { }; return { - nodes: nodes.filter((node) => !node.hidden && node.width && node.height), viewBB, boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes, s.nodeOrigin), viewBB) : viewBB, rfId: s.rfId, @@ -46,8 +36,6 @@ const selector = (s: ReactFlowState) => { }; }; -const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); - const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; function MiniMap({ @@ -60,7 +48,7 @@ function MiniMap({ nodeStrokeWidth = 2, // We need to rename the prop to be `CapitalCase` so that JSX will render it as // a component properly. - nodeComponent: NodeComponent = MiniMapNode, + nodeComponent, maskColor = 'rgb(240, 240, 240, 0.6)', maskStrokeColor = 'none', maskStrokeWidth = 1, @@ -75,15 +63,9 @@ function MiniMap({ }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); - const { boundingRect, viewBB, nodes, rfId, nodeOrigin, panZoom, translateExtent, flowWidth, flowHeight } = useStore( - selector, - shallow - ); + const { boundingRect, viewBB, rfId, panZoom, translateExtent, flowWidth, flowHeight } = useStore(selector, shallow); const elementWidth = (style?.width as number) ?? defaultWidth; const elementHeight = (style?.height as number) ?? defaultHeight; - const nodeColorFunc = getAttrFunction(nodeColor); - const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); - const nodeClassNameFunc = getAttrFunction(nodeClassName); const scaledWidth = boundingRect.width / elementWidth; const scaledHeight = boundingRect.height / elementHeight; const viewScale = Math.max(scaledWidth, scaledHeight); @@ -94,7 +76,6 @@ function MiniMap({ const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; const width = viewWidth + offset * 2; const height = viewHeight + offset * 2; - const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; const viewScaleRef = useRef(0); const minimapInstance = useRef(); @@ -136,10 +117,10 @@ function MiniMap({ : undefined; const onSvgNodeClick = onNodeClick - ? (event: MouseEvent, nodeId: string) => { + ? useCallback((event: MouseEvent, nodeId: string) => { const node = store.getState().nodeInternals.get(nodeId)!; onNodeClick(event, node); - } + }, []) : undefined; return ( @@ -159,28 +140,15 @@ function MiniMap({ onClick={onSvgClick} > {ariaLabel && {ariaLabel}} - {nodes.map((node) => { - const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute; - - return ( - - ); - })} + { +}: MiniMapNodeProps) { const { background, backgroundColor } = style || {}; const fill = (color || background || backgroundColor) as string; @@ -36,8 +37,6 @@ const MiniMapNode = ({ onClick={onClick ? (event) => onClick(event, id) : undefined} /> ); -}; - -MiniMapNode.displayName = 'MiniMapNode'; +} export default memo(MiniMapNode); diff --git a/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx new file mode 100644 index 00000000..adbd9810 --- /dev/null +++ b/packages/react/src/additional-components/MiniMap/MiniMapNodes.tsx @@ -0,0 +1,65 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { memo } from 'react'; +import { shallow } from 'zustand/shallow'; +import { getNodePositionWithOrigin } from '@xyflow/system'; + +import { useStore } from '../../hooks/useStore'; +import type { ReactFlowState } from '../../types'; +import MiniMapNode from './MiniMapNode'; +import type { MiniMapNodes, GetMiniMapNodeAttribute } from './types'; + +declare const window: any; + +const selector = (s: ReactFlowState) => s.nodeOrigin; +const selectorNodes = (s: ReactFlowState) => s.getNodes().filter((node) => !node.hidden && node.width && node.height); +const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); + +function MiniMapNodes({ + nodeStrokeColor = 'transparent', + nodeColor = '#e2e2e2', + nodeClassName = '', + nodeBorderRadius = 5, + nodeStrokeWidth = 2, + // We need to rename the prop to be `CapitalCase` so that JSX will render it as + // a component properly. + nodeComponent: NodeComponent = MiniMapNode, + onClick, +}: MiniMapNodes) { + const nodes = useStore(selectorNodes, shallow); + const nodeOrigin = useStore(selector); + const nodeColorFunc = getAttrFunction(nodeColor); + const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); + const nodeClassNameFunc = getAttrFunction(nodeClassName); + + const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; + + return ( + <> + {nodes.map((node) => { + const { x, y } = getNodePositionWithOrigin(node, node.origin || nodeOrigin).positionAbsolute; + + return ( + + ); + })} + + ); +} + +export default memo(MiniMapNodes); diff --git a/packages/react/src/additional-components/MiniMap/types.ts b/packages/react/src/additional-components/MiniMap/types.ts index c51ca69c..870cbf0b 100644 --- a/packages/react/src/additional-components/MiniMap/types.ts +++ b/packages/react/src/additional-components/MiniMap/types.ts @@ -26,7 +26,14 @@ export type MiniMapProps = Omit, ' zoomStep?: number; }; -export interface MiniMapNodeProps { +export type MiniMapNodes = Pick< + MiniMapProps, + 'nodeColor' | 'nodeStrokeColor' | 'nodeClassName' | 'nodeBorderRadius' | 'nodeStrokeWidth' | 'nodeComponent' +> & { + onClick?: (event: MouseEvent, nodeId: string) => void; +}; + +export type MiniMapNodeProps = { id: string; x: number; y: number; @@ -40,4 +47,4 @@ export interface MiniMapNodeProps { strokeWidth: number; style?: CSSProperties; onClick?: (event: MouseEvent, id: string) => void; -} +};