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

React Leaflet创建图层提交API时Context中activeTask为空如何处理

问题原因

这是典型的React闭包陷阱问题:
组件首次渲染时,_onCreated 函数被创建,此时闭包捕获的是初始状态的空activeTask。react-leaflet-draw的EditControl组件只会在首次挂载时初始化一次Leaflet.draw实例,并且读取一次传入的onCreated回调。后续activeTask更新时,虽然组件重渲染生成了新的_onCreated函数(已经捕获了最新的非空activeTask),但Leaflet.draw实例内部依然持有最初的旧回调,所以执行时读取的永远是初始的空activeTask。

解决方案

推荐用useRef存储最新的activeTask值,不受闭包影响:

  1. 导入useRef和useEffect
  2. 用ref同步activeTask的最新值
  3. 回调函数中直接读取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='&copy; <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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 16:18:04