You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React 18中替代ReactDOM.render集成TomTomMap自定义Marker的方案

在React 18中为第三方地图(TomTom/Mapbox)实现可绑定React事件的自定义Marker

问题核心

你之前用React 18已废弃的ReactDOM.render实现了自定义Marker,但现在需要替换成合规方案,同时保证Marker组件能正常绑定React事件、更新React状态,而非只能用原生DOM事件。

方案1:用React 18的createRoot替代ReactDOM.render

这是最直接的替换方案,完全兼容React 18规范,同时保留原有逻辑,让Marker组件处于React上下文内,正常响应事件和状态变化。

修改后的完整代码:

import { createRoot } from 'react-dom/client';
import { useRef, useEffect } from 'react';

const Marker = ({ onClick, children, feature }) => {
  const handleClick = () => {
    onClick(feature.properties.description);
  };

  return (
    <button onClick={handleClick} className="marker">
      {children}
    </button>
  );
};

const Map = () => {
  const mapContainerRef = useRef(null);
  // 存储所有React根实例,用于卸载时清理
  const markerRoots = useRef([]);

  useEffect(() => {
    // 初始化地图(TomTom的话替换成tt.Map即可)
    const map = new mapboxgl.Map({
      container: mapContainerRef.current,
      style: "mapbox://styles/mapbox/streets-v11",
      center: [-87.65, 41.84],
      zoom: 10,
    });

    geoJson.features.forEach((feature) => {
      const markerDiv = document.createElement('div');
      // 创建React根节点
      const root = createRoot(markerDiv);
      markerRoots.current.push(root);

      // 渲染React Marker组件
      root.render(<Marker onClick={handleMarkerClick} feature={feature} />);

      // 创建地图Marker并添加到地图
      new mapboxgl.Marker(markerDiv)
        .setLngLat(feature.geometry.coordinates)
        .addTo(map);
    });

    map.addControl(new mapboxgl.NavigationControl(), 'top-right');

    // 清理逻辑:卸载地图和所有React根节点
    return () => {
      map.remove();
      markerRoots.current.forEach(root => root.unmount());
      markerRoots.current = [];
    };
  }, []);

  const handleMarkerClick = (title) => {
    // 这里可以直接更新React状态,比如调用setState
    window.alert(title);
  };

  return <div className="map-container" ref={mapContainerRef} />;
};

export default Map;

关键改动

  • 用createRoot替代废弃的ReactDOM.render,符合React 18的渲染规范
  • 记录所有创建的React根实例,组件卸载时调用unmount()清理,避免内存泄漏
  • Marker组件完全处于React上下文,onClick等React事件正常触发,也能访问父组件的状态和方法

方案2:封装自定义Hook(复用性更强)

如果需要在多个组件中使用自定义Marker,可以把逻辑封装成Hook,让代码更简洁、易复用:

import { createRoot } from 'react-dom/client';
import { useRef, useEffect } from 'react';

// 自定义Hook:处理地图Marker的创建和React组件挂载
export const useMapMarker = (map, feature, Component, componentProps) => {
  const rootRef = useRef(null);
  const markerRef = useRef(null);

  useEffect(() => {
    if (!map) return;

    const markerDiv = document.createElement('div');
    const root = createRoot(markerDiv);
    rootRef.current = root;

    // 渲染传入的React组件
    root.render(<Component {...componentProps} feature={feature} />);

    // 创建地图Marker(TomTom替换成tt.Marker)
    const marker = new mapboxgl.Marker(markerDiv)
      .setLngLat(feature.geometry.coordinates)
      .addTo(map);
    markerRef.current = marker;

    // 清理
    return () => {
      root.unmount();
      marker.remove();
    };
  }, [map, feature, Component, componentProps]);

  return markerRef;
};

在Map组件中使用这个Hook:

const Map = () => {
  const mapContainerRef = useRef(null);
  const mapRef = useRef(null);

  useEffect(() => {
    const map = new mapboxgl.Map({
      container: mapContainerRef.current,
      style: "mapbox://styles/mapbox/streets-v11",
      center: [-87.65, 41.84],
      zoom: 10,
    });
    mapRef.current = map;

    map.addControl(new mapboxgl.NavigationControl(), 'top-right');

    return () => map.remove();
  }, []);

  const handleMarkerClick = (title) => {
    console.log('点击了Marker:', title);
    // 示例:更新React状态
    // setSelectedMarkerTitle(title);
  };

  return (
    <div className="map-container" ref={mapContainerRef}>
      {geoJson.features.map((feature) => (
        <React.Fragment key={feature.id}>
          {useMapMarker(mapRef.current, feature, Marker, { onClick: handleMarkerClick })}
        </React.Fragment>
      ))}
    </div>
  );
};

优势

  • 逻辑分离,Map组件只负责地图初始化,Marker渲染逻辑封装在Hook中
  • 复用性强,其他地图组件可以直接调用该Hook创建自定义Marker
  • 自动处理清理,无需手动管理根节点和Marker的卸载

注意事项

  • 无论哪种方案,必须在组件卸载时清理React根节点和地图Marker,防止内存泄漏
  • 切换到TomTom地图时,只需要把mapboxgl.Map和mapboxgl.Marker替换为TomTom对应的API(如tt.map、tt.Marker),核心逻辑完全通用
  • Marker组件内可以正常使用React的状态、Hook(如useState、useEffect),因为它处于React上下文环境中

内容的提问来源于stack exchange,提问作者Jacob Nordstroem Hansen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 12:37:54