返回

解决 VueFlow 自定义节点连接难题

vue.js

解决 VueFlow 自定义节点连接问题

在使用 VueFlow 实现拖拽自定义节点功能时,有时会遇到连接失败的情况,例如报 "<path> attribute d: Expected number" 错误,或者拖拽连接后出现默认节点等问题。 本文分析这些问题的可能原因,并提供解决方案。

问题分析与解决方案

出现 "<path> attribute d: Expected number" 错误,通常是连接线 (edge) 的计算出现问题,导致 path 属性的值不正确。 这通常和节点的位置或者大小计算有关。此外,如果连接时出现默认节点而不是预期的自定义节点,可能与节点类型未正确注册或 Handle 组件的配置有关。

1. 连接线 path 错误:尺寸计算和节点更新时机

原因: VueFlow 在计算连接线路径时,依赖于节点的准确位置和尺寸信息。如果这些信息在连接发生时没有正确同步,就会导致计算错误。特别是在节点是动态渲染或使用了 Vuetify 等 UI 库的情况下,节点尺寸的获取可能存在延迟。

解决方案: 确保节点在渲染完成后再进行连接计算,并避免使用可能导致尺寸获取错误的内联样式。延迟连接的生成或手动更新节点位置能规避此问题。

代码示例:

假设 InputNode 组件中使用 v-card 组件时尺寸计算出现延迟,可以通过 $nextTick 确保组件渲染完成后再尝试连接:

<template>
  <div>
    <Handle id="input-target" type="target" :position="Position.Right" />
    <v-card ref="card" :width="'300px'" :height="'104px'">
      <v-card-title>Input</v-card-title>
      <v-card-text>
        <v-text-field label="Input" outlined hide-details density="compact" />
      </v-card-text>
    </v-card>
    <Handle id="input-source" type="source" :position="Position.Left" />
  </div>
</template>

<script setup>
import { Handle, Position, useVueFlow } from '@vue-flow/core';
import { onMounted, ref } from 'vue';

const { updateNode } = useVueFlow()

const props = defineProps({
  id: { type: String, required: false },
  data: { type: Object, required: false },
});

const card = ref(null)

onMounted(() => {
  // 等待 v-card 渲染完成后更新节点信息
  setTimeout(() => { // 使用 setTimeout 给予 v-card 更多时间完成渲染
    if (props.id && card.value) {
        //尝试直接获取 dom 并赋值
        const element = card.value.$el;
        const width = element.offsetWidth;
        const height = element.offsetHeight;
        updateNode(props.id, (node) => ({ dimensions: { width, height } }))

    }
  }, 0);

});

</script>

操作步骤:

  1. InputNode 组件中添加 ref="card"v-card 组件。
  2. 使用 onMounted 生命周期钩子,在组件挂载后等待下一个 DOM 更新周期。
  3. 通过 card.value.$el.offsetWidthcard.value.$el.offsetHeight 获取元素的实际宽度和高度。
  4. 调用 updateNode 更新 VueFlow 中对应节点的尺寸信息。

安全建议: 建议使用防抖或节流等技术来限制更新频率,避免过度渲染导致性能问题。 考虑到UI组件库的复杂性,直接读取 DOM 元素进行赋值是一个比较稳妥的策略,虽然侵入性比较高,但是简单有效。
考虑到UI组件库的复杂性,手动设置明确的尺寸能够避免更多的后续问题。

2. 拖拽后出现默认节点

原因: onConnect 函数没有正确处理自定义节点的连接。 当从 InputNode 拖拽到 StartNode 时,可能没有找到对应的节点类型,导致 VueFlow 默认创建了一个普通节点。

解决方案: 确保 onConnect 函数正确处理自定义节点的连接逻辑,并将 Handle 组件正确配置。 确保拖拽的节点的 type 被正确的识别,如果发现 type 没有正确的注册到 vueflow 中需要检查注册代码以及传值。

代码示例:

检查主文件中 onConnect 函数是否调用 addEdges。 如果发现类型不正确则通过 debug 的方式, 向上检查节点创建和节点类型绑定的代码逻辑。 重点排查 drag-n-drop.js 中是否已经正确获得了节点的类型,保证在拖拽的时候类型没有丢失。

<script setup>
import { ref, markRaw } from 'vue';
import { VueFlow, useVueFlow, ConnectionMode } from '@vue-flow/core';
import useDragAndDrop from './drag-n-drop.js';
import StartNode from './nodes/StartNode.vue';
import InputNode from './nodes/InputNode.vue';

const { onConnect, addEdges } = useVueFlow();

const { onDragOver, onDrop, onDragLeave, onDragStart } = useDragAndDrop()

const nodes = ref( [
    {
        id: 'start-node',
        type: 'start',
        position: { x: 0, y: 50 },
        dimensions: { width: '150px', height: '50px' },
    },
] );

const edges = ref( [] );

const types = {
    start: markRaw( StartNode ),
    input: markRaw( InputNode ),
};

// 确保 addEdges 被正确调用,如果出现问题可以在该方法内部增加断点
onConnect( (connection) => {
    addEdges( connection );
} );
</script>

确保节点类型已经被 markRaw 正确注册,否则 Vue 可能会修改 props 并导致奇怪的问题。

const types = {
    start: markRaw( StartNode ),
    input: markRaw( InputNode ),
};

操作步骤:

  1. 检查 VueFlow 组件的 node-types 属性是否正确设置,包含所有自定义节点类型。
  2. 确认 Handle 组件的 idtype 属性正确配置,并且 type 与节点类型对应。

安全建议: 对所有传入 addEdges 的连接数据进行验证,确保数据格式正确,避免潜在的安全风险。
使用 try catch 来包裹 addEdges 调用能够保证出现错误的时候程序不会崩溃,从而方便后续调试。

总结

解决 VueFlow 自定义节点连接问题,关键在于确保节点的位置、尺寸等信息正确,并且 onConnect 函数能够正确处理自定义节点的连接逻辑。仔细检查代码中的节点类型注册、尺寸更新、Handle 组件配置以及 onConnect 函数的处理,通常能够找到并解决问题。 通过调试可以进一步加深理解问题和相关知识。