React Leaflet创建图层提交API时Context中activeTask为空如何处理
问题原因
这是典型的React闭包陷阱问题:
组件首次渲染时,_onCreated 函数被创建,此时闭包捕获的是初始状态的空activeTask。react-leaflet-draw的EditControl组件只会在首次挂载时初始化一次Leaflet.draw实例,并且读取一次传入的onCreated回调。后续activeTask更新时,虽然组件重渲染生成了新的_onCreated函数(已经捕获了最新的非空activeTask),但Leaflet.draw实例内部依然持有最初的旧回调,所以执行时读取的永远是初始的空activeTask。
解决方案
推荐用useRef存储最新的activeTask值,不受闭包影响:
- 导入
useRef和useEffect - 用ref同步
activeTask的最新值 - 回调函数中直接读取ref的current属性
修改后的核心代码如下:
import React, { useContext, useRef, useEffect } from 'react'; import axios from 'axios'; import { MapContainer, TileLayer, Marker, Popup, FeatureGroup, GeoJSON } from 'react-leaflet'; import { EditControl } from 'react-leaflet-draw'; import 'leaflet/dist/leaflet.css'; import 'leaflet-draw/dist/leaflet.draw.css'; import { DataContext } from './DataContext'; const LeafletComponent = () => { const { dataContext, activeTaskContext } = useContext(DataContext); const [activeTask, setActiveTask] = activeTaskContext; const [data, setData] = dataContext; // 新增ref存储最新的activeTask const activeTaskRef = useRef(activeTask); // 每次activeTask更新时同步到ref useEffect(() => { activeTaskRef.current = activeTask; }, [activeTask]); const position = [51.505, -0.09] let listAreas = []; for (let i = 0; i < data.length; i++) { if ("taskArea" in data[i]) { for (let j = 0; j < data[i].taskArea.length; j++) { if ("areaBoundaries" in data[i].taskArea[j]) { listAreas.push(<GeoJSON key={data[i].taskArea[j]._id} data={JSON.parse(data[i].taskArea[j].areaBoundaries)} />); } } } } console.log("activeTask 1"); console.log(activeTask); const _onCreated = async (e) => { console.log("activeTask 2"); console.log(activeTaskRef.current); // 此处可拿到最新的activeTask const layer = e.layer; const geoj = layer.toGeoJSON(); const postData = { taskId: activeTaskRef.current?._id, // 从ref取值 areaBoundaries: JSON.stringify(geoj) } try { const response = await axios.post('http://localhost:3333/createarea', postData); // do something with response } catch (error) { console.log(error); } }; return ( <MapContainer center={position} zoom={13} scrollWheelZoom={true}> <FeatureGroup> <EditControl position="topright" onCreated={_onCreated} draw={{ marker: false, circle: false, circlemarker: false, rectangle: true, polyline: false, polygon: true }} /> </FeatureGroup> <TileLayer attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> {listAreas} </MapContainer> ) } export default LeafletComponent;
备选方案
也可以给EditControl添加依赖activeTask的key,强制每次activeTask变化时重新渲染EditControl,绑定最新的回调:
<EditControl key={activeTask?._id || 'empty'} position="topright" onCreated={_onCreated} draw={{ marker: false, circle: false, circlemarker: false, rectangle: true, polyline: false, polygon: true }} />
该方案缺点是每次切换激活任务时,绘图控件会重新加载,可能清空未完成的绘图操作,适用性不如useRef方案。
内容的提问来源于stack exchange,提问作者Bananajoe
相关产品推荐
相关产品推荐

