React+TypeScript中点击Leaflet Marker调用setState的问题解决
问题
我通过Array.map渲染设备列表,使用Context API维护[selectedEquipmentId, setSelectedEquipmentId]状态。希望点击<Marker/>组件时调用setSelectedEquipmentId(equipmentId),但常规的onClick操作在Leaflet中无效,尝试多种方法均未解决。
相关代码
Map组件代码
function Map() { const equipmentsPositions = useContext(EquipmentsPositionContext); const setSelectedEquipmentId = useContext(EquipmentIdContext); const center: LatLngExpression = [-19.151801, -46.007759]; function getEquipmentById(equipmentId: string) { const filteredEquipment = equipmentsStateHistory.filter( (equipment) => equipment.equipmentId === equipmentId ); return filteredEquipment[0]; } return ( <Box h="100vh" w="80%"> <MapContainer center={center} zoom={10} scrollWheelZoom={true} style={{ height: "100%", width: "100%" }} > <TileLayer attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> {equipmentsPositions?.map((equipmentPosition) => { const equipment = getEquipmentById(equipmentPosition.equipmentId); return ( <Marker onClick={() => {setSelectedEquipmentId(equipment.equipmentId)}} key={equipment.equipmentId} position={[ equipmentPosition.positions[equipmentsPositions.length - 1].lat, equipmentPosition.positions[equipmentsPositions.length - 1].lon, ]} > <Popup> <Text as="span" color={equipment.color}> {equipment.name} </Text> </Popup> </Marker> ); })} </MapContainer> </Box> ); } export default Map;
Context代码
interface ISelectedEquipmentIdContext { selectedEquipmentId: string | ReactNode; setSelectedEquipmentId: React.Dispatch<React.SetStateAction<string>> } export const EquipmentIdContext = createContext<ISelectedEquipmentIdContext | null>( null ); export function EquipmentIdProvider({ children }: IProps) { const [selectedEquipmentId, setSelectedEquipmentId] = useState<string>(""); return ( <EquipmentIdContext.Provider value={{ selectedEquipmentId, setSelectedEquipmentId }}> {children} </EquipmentIdContext.Provider> ); }
解决方案
1. 修正Context的获取逻辑
你当前直接从EquipmentIdContext获取setSelectedEquipmentId是错误的,因为Context提供的是包含selectedEquipmentId和setSelectedEquipmentId的对象,而非单独的函数。需要解构获取,同时添加非空判断避免报错:
const { setSelectedEquipmentId } = useContext(EquipmentIdContext) || {};
2. 改用react-leaflet标准事件绑定方式
Leaflet的Marker组件推荐使用eventHandlers属性绑定事件,而非直接用onClick,这能避免框架事件绑定的兼容问题:
<Marker key={equipment.equipmentId} position={/* 坐标 */} eventHandlers={{ click: () => { // 确保equipment存在再执行状态更新 equipment?.equipmentId && setSelectedEquipmentId?.(equipment.equipmentId); } }} >
3. 增加空值校验
在渲染Marker前,先校验equipment是否存在,避免后续属性访问报错:
{equipmentsPositions?.map((equipmentPosition) => { const equipment = getEquipmentById(equipmentPosition.equipmentId); if (!equipment) return null; // 空值直接跳过渲染 // ... 渲染Marker })}
修正后的完整Map组件
function Map() { const equipmentsPositions = useContext(EquipmentsPositionContext); const { setSelectedEquipmentId } = useContext(EquipmentIdContext) || {}; const center: LatLngExpression = [-19.151801, -46.007759]; function getEquipmentById(equipmentId: string) { const filteredEquipment = equipmentsStateHistory.filter( (equipment) => equipment.equipmentId === equipmentId ); return filteredEquipment[0]; } return ( <Box h="100vh" w="80%"> <MapContainer center={center} zoom={10} scrollWheelZoom={true} style={{ height: "100%", width: "100%" }} > <TileLayer attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> {equipmentsPositions?.map((equipmentPosition) => { const equipment = getEquipmentById(equipmentPosition.equipmentId); if (!equipment) return null; return ( <Marker key={equipment.equipmentId} position={[ equipmentPosition.positions[equipmentsPositions.length - 1].lat, equipmentPosition.positions[equipmentsPositions.length - 1].lon, ]} eventHandlers={{ click: () => setSelectedEquipmentId?.(equipment.equipmentId) }} > <Popup> <Text as="span" color={equipment.color}> {equipment.name} </Text> </Popup> </Marker> ); })} </MapContainer> </Box> ); } export default Map;
额外检查
确认Map组件已被包裹在EquipmentIdProvider内部,否则useContext会返回null,导致setSelectedEquipmentId无法调用。
内容的提问来源于stack exchange,提问作者Roger Xavier
相关产品推荐
相关产品推荐

