dynamic-content
本文回答:setDOMContent + Vue / React 组件 —— 动态更新、事件绑定、组件卸载。
一、setDOMContent 的用法
setHTML 用字符串,setDOMContent 用现成的 Node:
const div = document.createElement("div");
div.innerHTML = `
<h3>站点</h3>
<button id="btn-save">保存</button>
`;
div.querySelector("#btn-save").addEventListener("click", () => alert("save"));
new vjmap.Popup().setDOMContent(div).setLngLat([lng, lat]).addTo(map);
优点:事件直接绑定,popup 打开后立即生效。
二、Vue 3 组件
2.1 渲染函数
<!-- SiteCard.vue -->
<template>
<div class="card">
<h3>{{ site.name }}</h3>
<p>温度 {{ site.temp }}℃</p>
<button @click="$emit('save')">保存</button>
</div>
</template>
import { createApp, h } from "vue";
import SiteCard from "./SiteCard.vue";
function makeCardPopup(site, handlers) {
const container = document.createElement("div");
const app = createApp({
render: () => h(SiteCard, { site, onSave: handlers.onSave })
});
app.mount(container);
const popup = new vjmap.Popup().setDOMContent(container);
popup.once("close", () => app.unmount()); // 关闭时销毁;用 once:close 会重复触发(见 §八)
return popup;
}
const popup = makeCardPopup(siteA, { onSave: () => console.log("saved") });
popup.setLngLat([lng, lat]).addTo(map);
2.2 Teleport 到 map 容器
或者用 <Teleport>:让 Popup DOM 结构保留在组件树内,但视觉上挂到 popup 容器。这要求你能拿到 popup.getElement()。
三、React 18
import ReactDOM from "react-dom/client";
import SiteCard from "./SiteCard";
function makeCardPopup(site) {
const container = document.createElement("div");
const root = ReactDOM.createRoot(container);
root.render(<SiteCard site={site} />);
const popup = new vjmap.Popup().setDOMContent(container);
popup.once("close", () => root.unmount()); // once:close 会重复触发(见 §八)
return popup;
}
四、更新 Popup 内容(不关闭)
popup.setHTML("新内容"); // 整体替换
popup.setText("纯文本");
popup.setDOMContent(newDiv); // 新的 DOM 节点
或直接操作 DOM:
const contentEl = popup.getElement().querySelector(".vjmapgis-popup-content");
contentEl.querySelector(".temperature").textContent = "28℃";
五、异步加载数据
const loader = document.createElement("div");
loader.textContent = "loading...";
const popup = new vjmap.Popup().setDOMContent(loader).setLngLat(ll).addTo(map);
fetchSite(id).then(data => {
const card = document.createElement("div");
card.innerHTML = `<h3>${data.name}</h3>`;
popup.setDOMContent(card);
});
六、Vue 响应式 + Popup
在 Vue 3 setup 里:
import { ref, watch } from "vue";
const temp = ref(26);
const card = document.createElement("div");
const app = createApp({
setup() {
return { temp };
},
template: `<div>温度 {{ temp }}℃</div>`
});
app.mount(card);
const popup = new vjmap.Popup().setDOMContent(card).setLngLat(ll).addTo(map);
setInterval(() => temp.value = Math.random() * 40, 1000);
七、表单 / input 在 Popup
<div id="form">
<input type="text" placeholder="名称" />
<button>确定</button>
</div>
const el = document.getElementById("form").cloneNode(true);
const popup = new vjmap.Popup({ focusAfterOpen: false }).setDOMContent(el);
popup.setLngLat(ll).addTo(map);
el.querySelector("button").addEventListener("click", () => {
alert("name: " + el.querySelector("input").value);
});
注意:设 focusAfterOpen: false 避免和输入法干扰。
八、Popup 关闭时清理
⚠️ close 在每次 remove() 时都触发(含从未打开时调 remove、对已打开的 popup 再 addTo 时);且引擎 remove 时漏解绑 preclick,closeOnClick: true 的 popup 关闭后每次点地图还会再触发 close。清理逻辑必须幂等:
let disposed = false;
popup.on("close", () => {
if (disposed) return;
disposed = true;
reactRoot.unmount(); // React
vueApp.unmount(); // Vue
clearInterval(timer); // 定时器
controller.abort(); // fetch controller
});
要复用 popup 再次打开时,在打开处把 disposed 复位并重新挂载内容。
九、性能:复用 Popup
不要每次都 new vjmap.Popup()。保持一个单例:
const popup = new vjmap.Popup({ closeButton: false });
function showPopup(lngLat, content) {
popup.setDOMContent(content).setLngLat(lngLat);
if (!popup.isOpen()) popup.addTo(map);
}
十、常见坑
- Vue/React 没 unmount → 内存泄漏:close 事件里清理(须幂等,见 §八)。
- setHTML 里的
<script>不执行(内部用 innerHTML);内联onclick="fn()"属性会执行,但只能调全局函数且受 CSP 限制——推荐 setDOMContent + addEventListener。 - 动态组件 props 更新 → 子组件能响应式更新;但 Popup 位置需另外 setLngLat。
- Popup 里放 map 组件 → 不支持嵌套地图;规避。