feat: performance 1

This commit is contained in:
2026-03-12 18:01:27 +01:00
parent 4bd2f1b458
commit 923bf4bff0
18 changed files with 404 additions and 146 deletions

View File

@@ -16,7 +16,7 @@
*/
import React, { useCallback, useContext, useMemo } from 'react'
import FlowContext from './flowContext'
import { GraphContext, ConnectionPathContext } from './flowContext'
import { nodePropsAreEqual } from './flowUtils'
import type { AppNode } from './nodeTypes'
@@ -71,13 +71,14 @@ export function useAbstractNode<TData = Record<string, unknown>>(
id: string,
data: TData
): AbstractNodeContext<TData> {
const ctx = useContext(FlowContext)
const nodes = ctx?.nodes ?? []
const edges = ctx?.edges ?? []
const setNodes = ctx?.setNodes
const setEdges = ctx?.setEdges
const graphCtx = useContext(GraphContext)
const pathCtx = useContext(ConnectionPathContext)
const nodes = graphCtx?.nodes ?? []
const edges = graphCtx?.edges ?? []
const setNodes = graphCtx?.setNodes
const setEdges = graphCtx?.setEdges
const addConnectionPathTrigger = ctx?.addConnectionPathTrigger
const addConnectionPathTrigger = pathCtx?.addConnectionPathTrigger
const updateData = useCallback(
(partial: Partial<TData>) => {
if (!setNodes) return

View File

@@ -1,18 +1,13 @@
/**
* FlowContext provides graph state, connection path state, and UI state to nodes and edges.
* Flow state is split into three contexts to reduce re-renders:
*
* ## 1. Graph state (nodes, edges)
* - nodes, setNodes, edges, setEdges owned by useCanvasGraph (history + persistence).
* - **GraphContext** nodes, edges, setNodes, setEdges. Changes on every graph edit.
* - **ConnectionPathContext** path/trigger/updating/paused/error state and callbacks.
* Only edges and path-aware nodes need this; graph edits don't change it.
* - **FlowUIContext** renaming, fullscreen, connectionFrom, flowActionsRef, isValidConnection.
*
* ## 2. Connection path state (edge visuals and path animation)
* - Nodes report lifecycle (updating / error / paused) via useSyncConnectionStatus() in nodeLifecycle.
* - Context holds: connectionPathUpdatingNodeIds, connectionPathTriggerNodeIds, connectionPathNodeIds,
* connectionPathPausedSegmentNodeIds, connectionPathActiveSegmentNodeIds, connectionPathPausedNodeIds,
* connectionPathErrorNodeIds, plus add/remove/start/end callbacks.
* - Edges use these in getConnectionStatus() for color (default / updating / paused / error). See connectionStatus.ts.
*
* ## 3. UI state
* - renamingNodeId, fullscreenNodeId, connectionFrom (drag-from handle), flowActionsRef, isValidConnection.
* Consumers subscribe only to what they need so that e.g. connection path ticks
* don't re-render every node, and node position changes don't re-render every edge.
*/
import React, { useMemo } from 'react'
@@ -21,58 +16,89 @@ import type { AppNode, AppEdge } from './nodeTypes'
export type ConnectionFrom = { nodeId: string; sourceHandle?: string } | null
/** Role of a node in the current connection path update: pushing data, receiving/loading, or just on path. */
/** Role of a node in the current connection path update. */
export type ConnectionPathRole = 'trigger' | 'updating' | 'on-path'
export type FlowActions = {
pasteAtViewportCenter: () => void
fitView: () => void
pasteAtViewportCenter: () => void
fitView: () => void
}
export type FlowContextValue = {
// ---- Graph state ----
nodes: AppNode[]
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
edges: AppEdge[]
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
// ---------------------------------------------------------------------------
// Graph context (nodes, edges, setters)
// ---------------------------------------------------------------------------
// ---- UI state ----
renamingNodeId: string | null
setRenamingNodeId: (id: string | null) => void
connectionFrom: ConnectionFrom
setConnectionFrom: (v: ConnectionFrom) => void
isValidConnection: (connection: Connection) => boolean
flowActionsRef: React.MutableRefObject<FlowActions | null>
fullscreenNodeId: string | null
setFullscreenNodeId: (id: string | null) => void
// ---- Connection path state (edge status and path animation; see state.ts ConnectionPathState) ----
connectionPathUpdatingNodeIds: string[]
connectionPathTriggerNodeIds: string[]
addConnectionPathTrigger: (nodeId: string) => void
connectionPathNodeIds: Set<string>
connectionPathPausedSegmentNodeIds: Set<string>
connectionPathActiveSegmentNodeIds: Set<string>
connectionPathPausedNodeIds: string[]
addConnectionPathPausedNode: (nodeId: string) => void
removeConnectionPathPausedNode: (nodeId: string) => void
connectionPathErrorNodeIds: string[]
addConnectionPathError: (nodeId: string) => void
removeConnectionPathError: (nodeId: string) => void
startConnectionPathUpdate: (nodeId: string) => void
endConnectionPathUpdate: (nodeId: string) => void
export type GraphContextValue = {
nodes: AppNode[]
setNodes: (updater: AppNode[] | ((prev: AppNode[]) => AppNode[])) => void
edges: AppEdge[]
setEdges: (updater: AppEdge[] | ((prev: AppEdge[]) => AppEdge[])) => void
}
const GraphContext = React.createContext<GraphContextValue | null>(null)
export { GraphContext }
// ---------------------------------------------------------------------------
// Connection path context (edge status and path animation)
// ---------------------------------------------------------------------------
export type ConnectionPathContextValue = {
connectionPathUpdatingNodeIds: string[]
connectionPathTriggerNodeIds: string[]
addConnectionPathTrigger: (nodeId: string) => void
connectionPathNodeIds: Set<string>
connectionPathPausedSegmentNodeIds: Set<string>
connectionPathActiveSegmentNodeIds: Set<string>
connectionPathPausedNodeIds: string[]
addConnectionPathPausedNode: (nodeId: string) => void
removeConnectionPathPausedNode: (nodeId: string) => void
connectionPathErrorNodeIds: string[]
addConnectionPathError: (nodeId: string) => void
removeConnectionPathError: (nodeId: string) => void
startConnectionPathUpdate: (nodeId: string) => void
endConnectionPathUpdate: (nodeId: string) => void
}
const ConnectionPathContext = React.createContext<ConnectionPathContextValue | null>(null)
export { ConnectionPathContext }
// ---------------------------------------------------------------------------
// UI context (renaming, fullscreen, connection drag, shortcuts)
// ---------------------------------------------------------------------------
export type FlowUIContextValue = {
renamingNodeId: string | null
setRenamingNodeId: (id: string | null) => void
connectionFrom: ConnectionFrom
setConnectionFrom: (v: ConnectionFrom) => void
isValidConnection: (connection: Connection) => boolean
flowActionsRef: React.MutableRefObject<FlowActions | null>
fullscreenNodeId: string | null
setFullscreenNodeId: (id: string | null) => void
}
const FlowUIContext = React.createContext<FlowUIContextValue | null>(null)
export { FlowUIContext }
// ---------------------------------------------------------------------------
// Legacy single context (for gradual migration or components that need everything)
// ---------------------------------------------------------------------------
export type FlowContextValue = GraphContextValue & ConnectionPathContextValue & FlowUIContextValue
const FlowContext = React.createContext<FlowContextValue | null>(null)
export default FlowContext
// ---------------------------------------------------------------------------
// Hooks
// ---------------------------------------------------------------------------
/**
* Returns this node's role in the current path update for styling (pushing vs receiving).
* Use with BaseNode's connectionPathRole prop or data-path-role for CSS.
* Returns this node's role in the current path update for styling.
* Uses only ConnectionPathContext so nodes don't re-render on graph changes.
*/
export function useConnectionPathRole(nodeId: string | undefined): ConnectionPathRole | null {
const ctx = React.useContext(FlowContext)
const ctx = React.useContext(ConnectionPathContext)
return useMemo(() => {
if (!nodeId) return null
const triggers = ctx?.connectionPathTriggerNodeIds

View File

@@ -20,7 +20,7 @@
*/
import { useContext, useEffect, useRef } from 'react'
import FlowContext from './flowContext'
import { ConnectionPathContext } from './flowContext'
export type NodeLifecyclePhase = 'idle' | 'trigger' | 'updating' | 'paused' | 'error'
@@ -53,7 +53,7 @@ export function useSyncConnectionStatus(
nodeId: string,
state: NodeConnectionStatusState
): void {
const ctx = useContext(FlowContext)
const ctx = useContext(ConnectionPathContext)
const { updating, error, paused } = state
const prevRef = useRef({ updating: false, error: false, paused: false })