chore: Performance optimizations

# What's changed?

* Remove keys
* Stabilize props
* Force reset store on unmount
This commit is contained in:
Braks
2022-04-24 13:34:22 +02:00
parent ad07b5b6ea
commit b6d8b57859
9 changed files with 73 additions and 44 deletions
+5 -1
View File
@@ -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')
+8 -2
View File
@@ -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)
+5 -2
View File
@@ -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>
+13 -10
View File
@@ -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>
+5 -5
View File
@@ -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>
+7 -4
View File
@@ -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>
+6 -4
View File
@@ -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) =>