feat: performance 1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 })
|
||||
|
||||
|
||||
Reference in New Issue
Block a user