> **本文回答**：批量新增 / 修改时的策略 —— vjmapext 侧渲染自动按帧合并；vjmap 底座侧一次 `setData`。

## 一、规则一句话

**任何一个循环里有 >100 次实体 / source 修改 → 注意批处理**。但 vjmapext 与 vjmap 底座的批处理机制完全不同，别套错。

## 二、vjmapext 实体：直接循环

```js
for (const pt of data) {
  mapcadLayer.addEntity(new vjmapext.PointEnt(pt));
}
```

`addEntity` 只标脏；重绘由 `requestAnimationFrame` 合并，循环 1 万次也只渲染一帧。**没有 `beginBulk`/`endBulk`/`bulkRun` API**。要把整批记为一次 undo：

```js
mapcadLayer.undoManager.beginTransaction();
try {
  for (const pt of data) mapcadLayer.addEntity(new vjmapext.PointEnt(pt));
} finally {
  mapcadLayer.undoManager.endTransaction();
}
```

## 三、vjmap 底座 source：一次 setData

```js
// ❌ 错：10 次 setData
const fc = { type: 'FeatureCollection', features: [] };
for (const f of newFeatures) {
  fc.features.push(f);
  src.setData(fc);              // 每次都触发重绘
}

// ✅ 对：先凑齐再 setData
const fc2 = { type: 'FeatureCollection', features: [...oldFeatures, ...newFeatures] };
src.setData(fc2);
```

详见 [batch/geojson-bulk](../../../vjmap/references/batch/geojson-bulk.md)。

## 四、feature-state（只改样式）

```js
// 批量换色：不 setData，直接 feature-state
for (const f of features) {
  map.setFeatureState({ source: 'src', id: f.id }, { highlighted: true });
}
// layer 的 paint 用表达式读 state
map.addLineLayer('lyr', 'src', {
  lineColor: ['case',
    ['boolean', ['feature-state', 'highlighted'], false], '#f00',
    '#888'
  ]
});
```

1 万 feature 换色只要几十 ms。

## 五、FX 批量：addPoints

```js
// 两种写法等效（推荐后者，代码更简洁、直接拿 handle 数组）
for (const p of list) fx.addPoint('fire', { cad: p, size: 30 });
fx.addPoints('fire', list.map(p => ({ cad: p, size: 30 })));
```

1.0.1 的 `addPoints` 内部就是循环 `addPoint`，两者都只写 CPU 影子缓冲、在下一帧 draw 时合并成一次 GPU 上传——没有数量级的速度差。

## 六、多批混合添加

```js
for (const e of batch1) mapcadLayer.addEntity(e);
for (const e of batch2) mapcadLayer.addEntity(e);
// 同步循环内全部标脏，下一帧一次 rebuild
```

## 七、styleChanged 批量

改几百个实体颜色：

```js
for (const e of selected) {
  e.color = 0xff0000;
  e.styleChanged();
}
```

配合 `styleChanged`（见 [style-only-fast-path](style-only-fast-path.md)）更快。

## 八、Web Worker 预计算

大批量数据解析 / 转换应放 Worker：

```js
// worker.js
self.onmessage = e => {
  const list = parseCSV(e.data);    // 解析 CSV 大字符串
  const features = list.map(...);
  self.postMessage({ features }, [features]);   // transfer
};

// main
const w = new Worker(new URL('./worker.js', import.meta.url));
w.postMessage(csv);
w.onmessage = ev => {
  src.setData({ type: 'FeatureCollection', features: ev.data.features });
};
```

## 九、流式加载（分批 setData）

百万点分 10 批：

```js
const total = bigList;
const BATCH = 100_000;
let cursor = 0;
const fc = { type: 'FeatureCollection', features: [] };

function nextBatch() {
  const end = Math.min(cursor + BATCH, total.length);
  for (let i = cursor; i < end; i++) fc.features.push(total[i]);
  src.setData(fc);
  cursor = end;
  if (cursor < total.length) requestAnimationFrame(nextBatch);
}
nextBatch();
```

用户看到数据"流入"而不是一卡一卡。

## 十、删除：批量

```js
for (const id of ids) mapcadLayer.deleteEntity(id);
```

## 十一、事务包装

```js
mapcadLayer.undoManager.beginTransaction();
try {
  for (const e of entities) mapcadLayer.addEntity(e);
} finally {
  mapcadLayer.undoManager.endTransaction();
}
// 一次 Undo 撤回整批
```

## 十二、常见坑

1. **循环里穿插 await**：把添加摊到多个帧；同步循环一把加完。
2. **undoManager 事务忘 endTransaction**：后续 undo 记录全挂在未闭合事务里；必须 try/finally。
3. **setData 频繁调**：每次都全量重排索引；必须合并。
4. **循环里每条都 `entity.dynamic = true`**：dynamic 桶被撑爆；静态数据用 cold。
5. **addPoints 传错结构**：opts 对象里必须有 `cad / lngLat / mercator` 其中一个。

## 相关

- 实体批量 → [batch/entity-bulk-add](../batch/entity-bulk-add.md)
- GeoJSON 批量 → [batch/geojson-bulk](../../../vjmap/references/batch/geojson-bulk.md)
- 样式批量 → [batch/bulk-style-update](../batch/bulk-style-update.md)
- FX 批量 → [fx-quality](fx-quality.md)
