diff --git a/common/changes/@visactor/vchart/refactor-wordcloud-shape-tap-member-callback_2026-09-14.json b/common/changes/@visactor/vchart/refactor-wordcloud-shape-tap-member-callback_2026-09-14.json
new file mode 100644
index 0000000000..14eb5ab87d
--- /dev/null
+++ b/common/changes/@visactor/vchart/refactor-wordcloud-shape-tap-member-callback_2026-09-14.json
@@ -0,0 +1,11 @@
+{
+ "changes": [
+ {
+ "packageName": "@visactor/vchart",
+ "comment": "refactor: keep the word cloud shape afterRender tap as a stable member callback",
+ "type": "none"
+ }
+ ],
+ "packageName": "@visactor/vchart",
+ "email": "chendaxin.tk@bytedance.com"
+}
diff --git a/packages/vchart/__tests__/unit/series/word-cloud-shape-release.test.ts b/packages/vchart/__tests__/unit/series/word-cloud-shape-release.test.ts
index 0f0e9f9355..b9ba372a58 100644
--- a/packages/vchart/__tests__/unit/series/word-cloud-shape-release.test.ts
+++ b/packages/vchart/__tests__/unit/series/word-cloud-shape-release.test.ts
@@ -1,7 +1,7 @@
import { VChart } from '../../../src/vchart-all';
import { createDiv, removeDom } from '../../util/dom';
-type Tap = { name: string; fn: () => void };
+type Tap = { name: string; fn: (stage: unknown) => void };
const SVG_MASK =
'';
@@ -67,7 +67,7 @@ describe('word cloud shape afterRender tap', () => {
expect(getWordCloudTaps().length).toBe(0);
// series 已经 release,_option 为空,这个 tap 即使被别处留住也不能再抛错
- expect(() => tap.fn()).not.toThrow();
+ expect(() => tap.fn(vchart.getStage())).not.toThrow();
});
it('should not be registered twice when the layout finishes more than once', () => {
diff --git a/packages/vchart/src/series/word-cloud/base.ts b/packages/vchart/src/series/word-cloud/base.ts
index 31a5591569..e43759f61e 100644
--- a/packages/vchart/src/series/word-cloud/base.ts
+++ b/packages/vchart/src/series/word-cloud/base.ts
@@ -46,6 +46,8 @@ import { wordCloud } from '../../theme/builtin/common/series/word-cloud';
import { LayoutZIndex } from '../../constant/layout';
import { ChartEvent } from '../../core';
+const AFTER_WORDCLOUD_SHAPE_DRAW_TAP = 'afterWordcloudShapeDraw';
+
export type IBaseWordCloudSeriesSpec = Omit & { type: string };
export class BaseWordCloudSeries extends BaseSeries {
@@ -79,8 +81,18 @@ export class BaseWordCloudSeries void };
+ /** 形状词云布局完成后挂到 stage 上的 afterRender tap;引用稳定,便于 unTap 精确摘掉自己 */
+ protected _afterWordcloudShapeDraw = (stage: IStage) => {
+ stage.hooks.afterRender.unTap(AFTER_WORDCLOUD_SHAPE_DRAW_TAP, this._afterWordcloudShapeDraw);
+
+ // 布局是异步的,跑完之前 series 可能已经被 release,此时 _option 已置空
+ const globalInstance = this._option?.globalInstance;
+ if (!globalInstance) {
+ return;
+ }
+ this._option.dispatchEvent?.(ChartEvent.afterWordcloudShapeDraw, { instance: globalInstance });
+ globalInstance.getChart().getOption().performanceHook?.afterWordcloudShapeDraw?.(globalInstance);
+ };
protected _wordCloudConfig?: WordCloudConfigType;
protected _wordCloudShapeConfig?: WordCloudShapeConfigType;
@@ -397,29 +409,13 @@ export class BaseWordCloudSeries {
- // 布局是异步的,跑完时 series 可能已经被 release,此时 _option 已置空
const stage = this._option?.globalInstance?.getStage();
if (!stage) {
return;
}
- this._removeAfterWordcloudShapeDrawTap();
-
- const afterWordcloudShapeDraw = () => {
- // 需要等到真正渲染完成
- this._removeAfterWordcloudShapeDrawTap();
-
- const globalInstance = this._option?.globalInstance;
- if (!globalInstance) {
- return;
- }
- this._option.dispatchEvent?.(ChartEvent.afterWordcloudShapeDraw, {
- instance: globalInstance
- });
- globalInstance.getChart().getOption().performanceHook?.afterWordcloudShapeDraw?.(globalInstance);
- };
-
- this._afterWordcloudShapeDrawTap = { stage, fn: afterWordcloudShapeDraw };
- stage.hooks.afterRender.tap('afterWordcloudShapeDraw', afterWordcloudShapeDraw);
+ // 同一个 series 再次布局时,先摘掉上一轮还没被触发的那个
+ stage.hooks.afterRender.unTap(AFTER_WORDCLOUD_SHAPE_DRAW_TAP, this._afterWordcloudShapeDraw);
+ stage.hooks.afterRender.tap(AFTER_WORDCLOUD_SHAPE_DRAW_TAP, this._afterWordcloudShapeDraw);
},
dataIndexKey: DEFAULT_DATA_KEY,
text: wordSpec.formatMethod
@@ -559,19 +555,12 @@ export class BaseWordCloudSeries