唯杰地图 logo唯杰地图
VJMAP
EN

dynamic-content

vjmap/references/popup/dynamic-content.md 5.3 KB183 行
原文

本文回答: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);
}

十、常见坑

  1. Vue/React 没 unmount → 内存泄漏:close 事件里清理(须幂等,见 §八)。
  2. setHTML 里的 <script> 不执行(内部用 innerHTML);内联 onclick="fn()" 属性会执行,但只能调全局函数且受 CSP 限制——推荐 setDOMContent + addEventListener。
  3. 动态组件 props 更新 → 子组件能响应式更新;但 Popup 位置需另外 setLngLat。
  4. Popup 里放 map 组件 → 不支持嵌套地图;规避。