mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-25 22:19:40 +08:00
perf(flow): avoid PageComponent re-renders on helper-line mousemove
onNodeDrag stored helperLines in local PageComponent state and depended on the nodes array, so every drag tick re-rendered the whole canvas and recreated the callback. That surfaced as 300ms+ 'mousemove' handler violations while dragging. Move helperLines into a dedicated tiny store consumed only by the HelperLines overlay, and coalesce per-mousemove computation into one requestAnimationFrame that reads the latest nodes from the flow store at compute time. The drag callback no longer depends on nodes.
This commit is contained in:
@ -1,11 +1,8 @@
|
||||
import { useViewport } from "@xyflow/react";
|
||||
import { HelperLinesState } from "../helpers/helper-lines";
|
||||
import { useHelperLinesStore } from "@/stores/helperLinesStore";
|
||||
|
||||
interface HelperLinesProps {
|
||||
helperLines: HelperLinesState;
|
||||
}
|
||||
|
||||
export default function HelperLines({ helperLines }: HelperLinesProps) {
|
||||
export default function HelperLines() {
|
||||
const helperLines = useHelperLinesStore((state) => state.helperLines);
|
||||
const { x: viewportX, y: viewportY, zoom } = useViewport();
|
||||
|
||||
if (!helperLines.horizontal && !helperLines.vertical) {
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
useState,
|
||||
} from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import ForwardedIconComponent from "@/components/common/genericIconComponent";
|
||||
import FlowToolbar from "@/components/core/flowToolbarComponent";
|
||||
@ -33,18 +34,17 @@ import CustomLoader from "@/customization/components/custom-loader";
|
||||
import { track } from "@/customization/utils/analytics";
|
||||
import useApplyFlowToCanvas from "@/hooks/flows/use-apply-flow-to-canvas";
|
||||
import useAutoSaveFlow from "@/hooks/flows/use-autosave-flow";
|
||||
|
||||
import { useFlowEvents } from "@/hooks/flows/use-flow-events";
|
||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||
import { useAddComponent } from "@/hooks/use-add-component";
|
||||
import InspectionPanel from "@/pages/FlowPage/components/InspectionPanel";
|
||||
import { nodeColorsName } from "@/utils/styleUtils";
|
||||
import { isSupportedNodeTypes } from "@/utils/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useHelperLinesStore } from "../../../../stores/helperLinesStore";
|
||||
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import useVersionPreviewStore from "../../../../stores/versionPreviewStore";
|
||||
@ -72,11 +72,7 @@ import UpdateAllComponents from "../UpdateAllComponents";
|
||||
import { CanvasBadge } from "./components/CanvasBanner";
|
||||
import HelperLines from "./components/helper-lines";
|
||||
import VersionPreviewOverlay from "./components/VersionPreviewOverlay";
|
||||
import {
|
||||
getHelperLines,
|
||||
getSnapPosition,
|
||||
type HelperLinesState,
|
||||
} from "./helpers/helper-lines";
|
||||
import { getHelperLines, getSnapPosition } from "./helpers/helper-lines";
|
||||
import {
|
||||
MemoizedBackground,
|
||||
MemoizedCanvasControls,
|
||||
@ -531,18 +527,36 @@ export default function Page({
|
||||
[takeSnapshot, onConnect],
|
||||
);
|
||||
|
||||
const [helperLines, setHelperLines] = useState<HelperLinesState>({});
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const helperLineEnabled = useFlowStore((state) => state.helperLineEnabled);
|
||||
const setHelperLines = useHelperLinesStore((state) => state.setHelperLines);
|
||||
const helperLineRafRef = useRef<number | null>(null);
|
||||
|
||||
const cancelHelperLineFrame = useCallback(() => {
|
||||
if (helperLineRafRef.current !== null) {
|
||||
cancelAnimationFrame(helperLineRafRef.current);
|
||||
helperLineRafRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => cancelHelperLineFrame, [cancelHelperLineFrame]);
|
||||
|
||||
const onNodeDrag: OnNodeDrag = useCallback(
|
||||
(_, node) => {
|
||||
if (helperLineEnabled) {
|
||||
const currentHelperLines = getHelperLines(node, nodes);
|
||||
setHelperLines(currentHelperLines);
|
||||
if (!helperLineEnabled) return;
|
||||
// Coalesce per-mousemove work into one frame and read the latest nodes
|
||||
// from the store at compute time so this callback doesn't get recreated
|
||||
// (and the canvas doesn't re-render) on every node change.
|
||||
if (helperLineRafRef.current !== null) {
|
||||
cancelAnimationFrame(helperLineRafRef.current);
|
||||
}
|
||||
helperLineRafRef.current = requestAnimationFrame(() => {
|
||||
helperLineRafRef.current = null;
|
||||
const latestNodes = useFlowStore.getState().nodes;
|
||||
setHelperLines(getHelperLines(node, latestNodes));
|
||||
});
|
||||
},
|
||||
[helperLineEnabled, nodes],
|
||||
[helperLineEnabled, setHelperLines],
|
||||
);
|
||||
|
||||
const onNodeDragStart: OnNodeDrag = useCallback(
|
||||
@ -562,6 +576,7 @@ export default function Page({
|
||||
updateCurrentFlow({ nodes });
|
||||
setPositionDictionary({});
|
||||
setIsDragging(false);
|
||||
cancelHelperLineFrame();
|
||||
setHelperLines({});
|
||||
},
|
||||
[
|
||||
@ -571,6 +586,8 @@ export default function Page({
|
||||
edges,
|
||||
reactFlowInstance,
|
||||
setPositionDictionary,
|
||||
cancelHelperLineFrame,
|
||||
setHelperLines,
|
||||
],
|
||||
);
|
||||
|
||||
@ -1010,7 +1027,7 @@ export default function Page({
|
||||
>
|
||||
<UpdateAllComponents />
|
||||
<MemoizedBackground />
|
||||
{helperLineEnabled && <HelperLines helperLines={helperLines} />}
|
||||
{helperLineEnabled && <HelperLines />}
|
||||
</ReactFlow>
|
||||
<FlowBuildingComponent />
|
||||
{bannerVisible && (
|
||||
|
||||
14
src/frontend/src/stores/helperLinesStore.ts
Normal file
14
src/frontend/src/stores/helperLinesStore.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { create } from "zustand";
|
||||
import type { HelperLinesState } from "@/pages/FlowPage/components/PageComponent/helpers/helper-lines";
|
||||
|
||||
type HelperLinesStore = {
|
||||
helperLines: HelperLinesState;
|
||||
setHelperLines: (helperLines: HelperLinesState) => void;
|
||||
};
|
||||
|
||||
export const useHelperLinesStore = create<HelperLinesStore>((set) => ({
|
||||
helperLines: {},
|
||||
setHelperLines: (helperLines) => set({ helperLines }),
|
||||
}));
|
||||
|
||||
export default useHelperLinesStore;
|
||||
Reference in New Issue
Block a user