chore: Performance optimizations
# What's changed? * Remove keys * Stabilize props * Force reset store on unmount
This commit is contained in:
@@ -113,10 +113,14 @@ onMounted(() => {
|
|||||||
if (edge.value.targetX !== targetX) edge.value.targetX = targetX
|
if (edge.value.targetX !== targetX) edge.value.targetX = targetX
|
||||||
if (edge.value.targetY !== targetY) edge.value.targetY = targetY
|
if (edge.value.targetY !== targetY) edge.value.targetY = targetY
|
||||||
},
|
},
|
||||||
{ immediate: true, deep: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
edge.value = null
|
||||||
|
})
|
||||||
|
|
||||||
const slots = inject(Slots)
|
const slots = inject(Slots)
|
||||||
|
|
||||||
const name = ref(edge.value.type ?? 'default')
|
const name = ref(edge.value.type ?? 'default')
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const onSelectNodeHandler = (event: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
useResizeObserver(nodeElement, () =>
|
const observer = useResizeObserver(nodeElement, () =>
|
||||||
store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value, forceUpdate: true }]),
|
store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value, forceUpdate: true }]),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,6 +67,8 @@ onMounted(() => {
|
|||||||
{ flush: 'post' },
|
{ flush: 'post' },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onBeforeUnmount(() => observer.stop())
|
||||||
|
|
||||||
store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value, forceUpdate: true }])
|
store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value, forceUpdate: true }])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,9 +93,13 @@ watch(
|
|||||||
|
|
||||||
node.value.handleBounds = getHandleBounds(nodeElement.value, scale.value)
|
node.value.handleBounds = getHandleBounds(nodeElement.value, scale.value)
|
||||||
},
|
},
|
||||||
{ deep: true },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
nodeElement.value = undefined
|
||||||
|
node.value = null
|
||||||
|
})
|
||||||
|
|
||||||
store.updateNodePosition({ id: node.value.id, diff: { x: 0, y: 0 } })
|
store.updateNodePosition({ id: node.value.id, diff: { x: 0, y: 0 } })
|
||||||
|
|
||||||
const slots = inject(Slots)
|
const slots = inject(Slots)
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ export default (options?: Options): UseVueFlow => {
|
|||||||
scope.vueFlowId = name
|
scope.vueFlowId = name
|
||||||
|
|
||||||
onScopeDispose(() => {
|
onScopeDispose(() => {
|
||||||
storage.remove(name)
|
vueFlow!.$reset()
|
||||||
|
storage.remove(vueFlow!.id)
|
||||||
vueFlow = null
|
vueFlow = null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -116,7 +117,9 @@ export default (options?: Options): UseVueFlow => {
|
|||||||
|
|
||||||
if (!vueFlow) throw new Error('[vueflow]: store instance not found.')
|
if (!vueFlow) throw new Error('[vueflow]: store instance not found.')
|
||||||
|
|
||||||
if (scope) provide(VueFlow, vueFlow)
|
if (scope) {
|
||||||
|
provide(VueFlow, vueFlow)
|
||||||
|
}
|
||||||
|
|
||||||
return vueFlow
|
return vueFlow
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import NodeWrapper from '../../components/Nodes/NodeWrapper.vue'
|
import NodeWrapper from '../../components/Nodes/NodeWrapper.vue'
|
||||||
import { useVueFlow } from '../../composables'
|
import { GraphNode, SnapGrid } from '../../types'
|
||||||
|
|
||||||
const { store } = useVueFlow()
|
interface Props {
|
||||||
|
draggable?: boolean
|
||||||
|
selectable?: boolean
|
||||||
|
connectable?: boolean
|
||||||
|
snapToGrid?: boolean
|
||||||
|
snapGrid?: SnapGrid
|
||||||
|
nodes: GraphNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const draggable = (d?: boolean) => (typeof d === 'undefined' ? props.draggable : d)
|
||||||
|
const selectable = (s?: boolean) => (typeof s === 'undefined' ? props.selectable : s)
|
||||||
|
const connectable = (c?: boolean) => (typeof c === 'undefined' ? props.connectable : c)
|
||||||
|
const snapGrid = (sg?: SnapGrid) => (sg ?? props.snapToGrid ? props.snapGrid : undefined)
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
@@ -12,14 +26,14 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div class="vue-flow__nodes vue-flow__container">
|
<div class="vue-flow__nodes vue-flow__container">
|
||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
v-for="node of store.getNodes"
|
v-for="node of props.nodes"
|
||||||
:id="node.id"
|
:id="node.id"
|
||||||
:key="node.id"
|
:key="node.id"
|
||||||
:node="node"
|
:node="node"
|
||||||
:draggable="typeof node.draggable === 'undefined' ? store.nodesDraggable : !!node.draggable"
|
:draggable="draggable(node.draggable)"
|
||||||
:selectable="typeof node.selectable === 'undefined' ? store.elementsSelectable : !!node.selectable"
|
:selectable="selectable(node.selectable)"
|
||||||
:connectable="typeof node.connectable === 'undefined' ? store.nodesConnectable : !!node.connectable"
|
:connectable="connectable(node.connectable)"
|
||||||
:snap-grid="node.snapGrid ?? (store.snapToGrid ? store.snapGrid : undefined)"
|
:snap-grid="snapGrid(node.snapGrid)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -53,13 +53,7 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<UserSelection v-if="selectionKeyPressed" :key="`user-selection-${id}`" />
|
<UserSelection v-if="selectionKeyPressed" />
|
||||||
<NodesSelection v-if="store.nodesSelectionActive" :key="`nodes-selection-${id}`" />
|
<NodesSelection v-if="store.nodesSelectionActive" />
|
||||||
<div
|
<div class="vue-flow__pane vue-flow__container" @click="onClick" @contextmenu="onContextMenu" @wheel="onWheel" />
|
||||||
:key="`flow-pane-${id}`"
|
|
||||||
class="vue-flow__pane vue-flow__container"
|
|
||||||
@click="onClick"
|
|
||||||
@contextmenu="onContextMenu"
|
|
||||||
@wheel="onWheel"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useVueFlow, useZoomPanHelper, useWindow } from '../../composables'
|
|||||||
import { FlowExportObject, FlowInstance, Store, XYPosition } from '../../types'
|
import { FlowExportObject, FlowInstance, Store, XYPosition } from '../../types'
|
||||||
import { pointToRendererPoint } from '../../utils'
|
import { pointToRendererPoint } from '../../utils'
|
||||||
|
|
||||||
const { id, store } = useVueFlow()
|
const { store } = useVueFlow()
|
||||||
|
|
||||||
const untilDimensions = async (store: Store) => {
|
const untilDimensions = async (store: Store) => {
|
||||||
// if ssr we can't wait for dimensions, they'll never really exist
|
// if ssr we can't wait for dimensions, they'll never really exist
|
||||||
@@ -55,9 +55,9 @@ onMounted(async () => {
|
|||||||
await untilDimensions(store)
|
await untilDimensions(store)
|
||||||
|
|
||||||
ready.value = true
|
ready.value = true
|
||||||
store.instance = instance
|
store.instance = instance as FlowInstance
|
||||||
store.fitViewOnInit && instance.fitView()
|
store.fitViewOnInit && instance.fitView()
|
||||||
store.hooks.paneReady.trigger(instance)
|
store.hooks.paneReady.trigger(instance as FlowInstance)
|
||||||
})
|
})
|
||||||
|
|
||||||
const transform = computed(() => `translate(${store.viewport.x}px,${store.viewport.y}px) scale(${store.viewport.zoom})`)
|
const transform = computed(() => `translate(${store.viewport.x}px,${store.viewport.y}px) scale(${store.viewport.zoom})`)
|
||||||
@@ -68,13 +68,16 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="vue-flow__transformationpane vue-flow__container" :style="{ transform, opacity: ready ? undefined : 0 }">
|
||||||
:key="`transformation-pane-${id}`"
|
<NodeRenderer
|
||||||
class="vue-flow__transformationpane vue-flow__container"
|
:draggable="store.nodesDraggable"
|
||||||
:style="{ transform, opacity: ready ? undefined : 0 }"
|
:selectable="store.elementsSelectable"
|
||||||
>
|
:connectable="store.nodesConnectable"
|
||||||
<NodeRenderer :key="`node-renderer-${id}`" />
|
:nodes="store.getNodes"
|
||||||
<EdgeRenderer :key="`edge-renderer-${id}`" />
|
:snap-to-grid="store.snapToGrid"
|
||||||
|
:snap-grid="store.snapGrid"
|
||||||
|
/>
|
||||||
|
<EdgeRenderer />
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { clamp, clampPosition } from '../../utils'
|
|||||||
import SelectionPane from '../SelectionPane/SelectionPane.vue'
|
import SelectionPane from '../SelectionPane/SelectionPane.vue'
|
||||||
import Transform from './Transform.vue'
|
import Transform from './Transform.vue'
|
||||||
|
|
||||||
const { id, store, zoomActivationKeyCode, selectionKeyCode } = useVueFlow()
|
const { store, zoomActivationKeyCode, selectionKeyCode } = useVueFlow()
|
||||||
const viewportEl = templateRef<HTMLDivElement>('viewport', null)
|
const viewportEl = templateRef<HTMLDivElement>('viewport', null)
|
||||||
|
|
||||||
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
||||||
@@ -83,7 +83,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
d3Selection
|
d3Selection
|
||||||
?.on('wheel', (event: WheelEvent) => {
|
.on('wheel', (event: WheelEvent) => {
|
||||||
if (store.panOnScroll && !zoomKeyPressed.value) {
|
if (store.panOnScroll && !zoomKeyPressed.value) {
|
||||||
if (isWrappedWithClass(event, store.noWheelClassName)) return
|
if (isWrappedWithClass(event, store.noWheelClassName)) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -108,7 +108,7 @@ onMounted(() => {
|
|||||||
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
|
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
|
||||||
|
|
||||||
if (d3Selection && store.panOnScrollSpeed)
|
if (d3Selection && store.panOnScrollSpeed)
|
||||||
d3Zoom?.translateBy(
|
d3Zoom.translateBy(
|
||||||
d3Selection,
|
d3Selection,
|
||||||
-(deltaX / currentZoom) * store.panOnScrollSpeed,
|
-(deltaX / currentZoom) * store.panOnScrollSpeed,
|
||||||
-(deltaY / currentZoom) * store.panOnScrollSpeed,
|
-(deltaY / currentZoom) * store.panOnScrollSpeed,
|
||||||
@@ -163,10 +163,10 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div ref="viewport" :key="`viewport-${id}`" class="vue-flow__viewport vue-flow__container">
|
<div ref="viewport" class="vue-flow__viewport vue-flow__container">
|
||||||
<Transform>
|
<Transform>
|
||||||
<slot />
|
<slot />
|
||||||
</Transform>
|
</Transform>
|
||||||
<SelectionPane :key="`selection-pane-${id}`" />
|
<SelectionPane />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ const modelProps = useVModels(props, emit)
|
|||||||
const { id, hooks, getNodeTypes, getEdgeTypes, ...rest } = useVueFlow()
|
const { id, hooks, getNodeTypes, getEdgeTypes, ...rest } = useVueFlow()
|
||||||
|
|
||||||
const dispose = useWatch(modelProps, { id, hooks, getNodeTypes, getEdgeTypes, ...rest })
|
const dispose = useWatch(modelProps, { id, hooks, getNodeTypes, getEdgeTypes, ...rest })
|
||||||
onUnmounted(() => dispose())
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
dispose()
|
||||||
|
})
|
||||||
|
|
||||||
useHooks(emit, hooks.value)
|
useHooks(emit, hooks.value)
|
||||||
|
|
||||||
@@ -46,14 +49,14 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="vue-flow">
|
<div class="vue-flow">
|
||||||
<Viewport :key="`renderer-${id}`">
|
<Viewport>
|
||||||
<template #nodes>
|
<template #nodes>
|
||||||
<template v-for="nodeName of Object.keys(getNodeTypes)" :key="`node-${nodeName}-${id}`">
|
<template v-for="nodeName of Object.keys(getNodeTypes)">
|
||||||
<slot :name="`node-${nodeName}`" />
|
<slot :name="`node-${nodeName}`" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template #edges>
|
<template #edges>
|
||||||
<template v-for="edgeName of Object.keys(getEdgeTypes)" :key="`edge-${edgeName}-${id}`">
|
<template v-for="edgeName of Object.keys(getEdgeTypes)">
|
||||||
<slot :name="`edge-${edgeName}`" />
|
<slot :name="`edge-${edgeName}`" />
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
const addEdges: Actions['addEdges'] = (params) => {
|
const addEdges: Actions['addEdges'] = (params) => {
|
||||||
const curr = params instanceof Function ? params(state.edges) : params
|
const curr = params instanceof Function ? params(state.edges) : params
|
||||||
|
|
||||||
curr.forEach((param) => {
|
curr.reduce<GraphEdge[]>((acc, param) => {
|
||||||
const edge = addEdge(param, state.edges)
|
const edge = addEdge(param, state.edges)
|
||||||
if (edge) {
|
if (edge) {
|
||||||
const sourceNode = getters.getNode.value(edge.source)!
|
const sourceNode = getters.getNode.value(edge.source)!
|
||||||
@@ -284,16 +284,18 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
|||||||
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
const missingTarget = !targetNode || typeof targetNode === 'undefined'
|
||||||
if (missingSource) console.warn(`[vueflow]: Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
if (missingSource) console.warn(`[vueflow]: Couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||||
if (missingTarget) console.warn(`[vueflow]: Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
if (missingTarget) console.warn(`[vueflow]: Couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||||
if (missingTarget || missingSource) return
|
if (missingTarget || missingSource) return acc
|
||||||
|
|
||||||
state.edges.push({
|
acc.push({
|
||||||
...state.defaultEdgeOptions,
|
...state.defaultEdgeOptions,
|
||||||
...edge,
|
...edge,
|
||||||
sourceNode,
|
sourceNode,
|
||||||
targetNode,
|
targetNode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
return acc
|
||||||
|
}, state.edges)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateEdge: Actions['updateEdge'] = (oldEdge, newConnection) =>
|
const updateEdge: Actions['updateEdge'] = (oldEdge, newConnection) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user