jeecgboot/JeecgBoot · error

【addComponent】组件"${type}"已存在

Error message

【addComponent】组件"${type}"已存在

What it means

addComponent stores components in a module-level Map keyed by JVxeTypes. If a type is already present in componentMap, re-registering throws to prevent silent shadowing of an existing cell. This typically surfaces during hot-module replacement (HMR): the registration module re-executes on save and tries to re-add already-registered components. The code has an issues/860 workaround that assigns the map to window in DEV for HMR recovery, but the duplicate guard itself still throws on a plain re-import.

Source

Thrown at jeecgboot-vue3/src/components/jeecg/JVxeTable/src/componentMap.ts:50

  JVxeTypes.rowNumber,
  JVxeTypes.rowCheckbox,
  JVxeTypes.rowRadio,
  JVxeTypes.rowExpand,
];

/**
 * 注册组件
 *
 * @param type 组件 type
 * @param component Vue组件
 * @param spanComponent 显示组件,可空,默认为 JVxeNormalCell 组件
 */
export function addComponent(type: JVxeTypes, component: JVxeVueComponent, spanComponent?: JVxeVueComponent) {
  if (excludeKeywords.includes(type)) {
    throw new Error(`【addComponent】不能使用"${type}"作为组件的name,因为这是关键字。`);
  }
  if (componentMap.has(type)) {
    throw new Error(`【addComponent】组件"${type}"已存在`);
  }
  componentMap.set(type, component);
  if (spanComponent) {
    componentMap.set(type + spanEnds, spanComponent);
  }
  // 代码逻辑说明: 【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
  import.meta.env.DEV && (window[JVxeComponents] = componentMap);
}

export function deleteComponent(type: JVxeTypes) {
  componentMap.delete(type);
  componentMap.delete(type + spanEnds);
  // 代码逻辑说明: 【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
  import.meta.env.DEV && (window[JVxeComponents] = componentMap);
}

/** 定义内置自定义组件 */
export function definedComponent() {

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Guard the registration with a componentMap.has(type) check before calling addComponent, or call deleteComponent first.
  2. For HMR, ensure the registration runs once per module load and is idempotent (wrap in a flag or use the existing has-check).
  3. Eliminate double-import paths: register each type in exactly one module.
  4. If a plugin re-registers, move registration to app.initializeApp, not per-component install.

Example fix

// before — throws on HMR re-evaluation
addComponent(JVxeTypes.input, JVxeInputCell);

// after — idempotent registration
if (!componentMap.has(JVxeTypes.input)) {
  addComponent(JVxeTypes.input, JVxeInputCell);
}
Defensive patterns

Strategy: validation

Validate before calling

import { componentMap } from './componentMapStore';
function registerOnce(type, component, spanComponent?) {
  if (componentMap.has(type)) return; // idempotent
  addComponent(type, component, spanComponent);
}

Type guard

const isRegistered = (type: JVxeTypes): boolean => componentMap.has(type);

Try / catch

try {
  addComponent(type, component);
} catch (e) {
  // already registered (likely HMR); safe to ignore or call deleteComponent first
}

Prevention

When it happens

Trigger: Calling addComponent for a type that was registered earlier in the same module load — most commonly during Vite HMR when registerThirdComp.ts or componentMap.ts is re-evaluated. Also triggered by importing the registration module twice (e.g. once eagerly and once lazily), or by a plugin that registers on every mount.

Common situations: Vite dev server HMR after editing a cell component or the registration file; dual registration from both registerThirdComp.ts and an @jeecg/online package; a Vue plugin that re-runs install on every component usage.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/a2a36411222a6c79. Report an issue: GitHub.