-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
背景&目的
Motivation
Currently, vueNodesMap in registry.ts is a module-level global object, while register() accepts a per-instance lf parameter. This means lf.register() correctly scopes the Model/View to the specific LogicFlow instance, but vueNodesMap does not — leading to an inconsistency.
// registry.ts (current)
export const vueNodesMap = {} // ← global singleton
export function register (config, lf) {
vueNodesMap[type] = { component, effect } // ← global overwrite!
lf.register({ type, view, model }) // ← per-instance (correct)
}When the same node type is registered for multiple LogicFlow instances (e.g., nested sub-flows), the later register() call overwrites the earlier entry in vueNodesMap.
Problem Scenario
- Create an outer LogicFlow instance, register node type
A— its Vue component captures context A - Create an inner (nested) LogicFlow instance, register the same type
A— its Vue component captures context B, and overwritesvueNodesMap['A'] - Delete/destroy the inner LogicFlow instance — context B is now stale/empty
- Add a new node of type
Ato the outer LogicFlow - Result: The outer LogicFlow creates the node using the stale component from the destroyed inner instance (context B), instead of the original component (context A)
Concrete scenario
This happens with a Loop Node that embeds a sub-flow (inner LogicFlow). The loop's sub-flow registers the same node types as the outer flow. When the loop node is deleted from the canvas, all new nodes created afterward use the orphaned inner components — breaking rendering (e.g., missing icons, empty slot data).
Expected Behavior
Each LogicFlow instance should maintain its own vueNodesMap, so nested instances don't interfere with each other.
Proposal
Option A: Scope vueNodesMap per LogicFlow instance (recommended)
const vueNodesMaps = new WeakMap<LogicFlow, Record<string, VueNodeEntry>>()
export function register (config: VueNodeConfig, lf: LogicFlow) {
const { type, component, effect, view: CustomNodeView, model: CustomNodeModel } = config
if (!type)
throw new Error('You should specify type in config')
if (!vueNodesMaps.has(lf))
vueNodesMaps.set(lf, {})
vueNodesMaps.get(lf)![type] = { component, effect }
lf.register({
type,
view: CustomNodeView || VueNodeView,
model: CustomNodeModel || VueNodeModel,
})
}
export function getVueNodeConfig (type: string, lf: LogicFlow) {
return vueNodesMaps.get(lf)?.[type]
}Using WeakMap ensures entries are automatically garbage-collected when the LogicFlow instance is destroyed.
Option B: Guard against overwrite (minimal change)
export function register (config: VueNodeConfig, lf: LogicFlow) {
const { type, component, effect } = config
// Only set if not already registered (first-come wins)
if (!vueNodesMap[type]) {
vueNodesMap[type] = { component, effect }
}
lf.register({ type, view, model })
}This is simpler but less flexible — it prevents any update to an existing registration.
Environment
@logicflow/vue-node-registry:^1.0.18@logicflow/core:^2.0.16
Current Workaround
At the application level, we wrap the inner register() calls with a component that snapshots and restores vueNodesMap after mount:
const PreserveRegistrations = defineComponent({
setup (_, { slots }) {
const saved = { ...vueNodesMap }
onMounted(() => {
Object.assign(vueNodesMap, saved)
})
return () => slots.default?.()
},
})