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
相关产品推荐
相关产品推荐

