Reactjs React Redux如何在平移时加载地图标记而不重新命名所有内容?
我正在使用react+redux和react-lipperv3+API加载标记,以便根据平移行为动态地固定在地图上 我这样做是为了触发“dragend”事件,并计算地图边界、缩放级别和中心,以便创建适当的参数以输入API端点。这样,当用户平移地图时,标记将动态输入 标记数据保存在redux存储中,API调用通过侦听端点更改的useEffect触发 问题在于,似乎在每次平移时都会重新渲染所有标记,这使得应用程序变得紧张和缓慢。我希望只渲染新标记,而不重新渲染旧标记。此外,边界之外的旧标记应该简单地删除。然而,事实并非如此。我认为,只要标记有一个键,react redux就能够将旧数据与新数据进行比较,并且只渲染新组件 我的标记加载是通过createAyncThunk完成的,如下所示:Reactjs React Redux如何在平移时加载地图标记而不重新命名所有内容?,reactjs,redux,react-redux,leaflet,react-leaflet,Reactjs,Redux,React Redux,Leaflet,React Leaflet,我正在使用react+redux和react-lipperv3+API加载标记,以便根据平移行为动态地固定在地图上 我这样做是为了触发“dragend”事件,并计算地图边界、缩放级别和中心,以便创建适当的参数以输入API端点。这样,当用户平移地图时,标记将动态输入 标记数据保存在redux存储中,API调用通过侦听端点更改的useEffect触发 问题在于,似乎在每次平移时都会重新渲染所有标记,这使得应用程序变得紧张和缓慢。我希望只渲染新标记,而不重新渲染旧标记。此外,边界之外的旧标记应该简单地
export const getCities = createAsyncThunk("markers/getCities", async (endpoint, thunkAPI) => {
try {
const response = await axios.get(endpoint);
return response.data;
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
});
使用以下切片:
// CREATE SLICE
const markerSlice = createSlice({
name: "markers",
initialState: {
cities: [],
markerType: "cities",
endpoint: "/api/get/cities",
},
reducers: {
// some reducer code
},
extraReducers: (builder) => {
builder.addCase(getCities.pending, (state) => {
state.cities = [];
});
builder.addCase(getCities.fulfilled, (state, { payload }) => {
state.cities = payload;
});
builder.addCase(getCities.rejected,(state, action) => {
state.loading = "error";
});
}
});
我的地图组件是这样的(为了可读性而简化):
import React,{useffect}来自“React”;
从“react传单”导入{MapContainer、Marker、Popup、TileLayer、useMap、useMapEvents};
导入“../../css/app.css”;
从“react-redux”导入{useSelector,useDispatch,batch};
从“./features/markerSlice”导入{getCities,setEndpoint};
从“./features/maplice”导入{setLatBnd,setLngBnd,setZoom,setLat,setLng};
导出常量LeafMap=()=>{
const dispatch=usedpatch();
//标记状态(标记数据)
const stateMarker=useSelector(state=>state.marker);
//地图状态(中心、缩放级别、边界等)
const stateMap=useSelector(state=>state.map);
//这将调用API来检索数据
useffect(()=>{
调度(getCities(stateMarker.endpoint));
},[stateMarker.endpoint]);
//用于渲染标记的自定义标记组件
常量GetMarkers=()=>{
const markerType=stateMarker.markerType;
返回stateMarker.cities.map((el,i)=>(
));
};
//这是MapContainer(传单v3)的子组件,用于侦听映射事件
函数GetMapProperties(){
const map=useMap();
//监听拖动事件结束
useMapEvents({
德拉根德:()=>{
//获取映射信息以便为API调用创建端点参数
const bounds=map.getBounds();
常量latBnd=边界[''东北'].lat
常量lngBnd=边界[''东北'].lng
const zoom=map.getZoom();
const lat=map.getCenter().lat;
const lng=map.getCenter().lng;
//更新触发API调用的端点(通过顶部的useEffect)
调度(setEndpoint({type:“trees”,lat:lat,lng:lng,latbnd:latbnd,lngbnd:lngbnd}))
},
});
//基于是否有可用数据渲染组件
如果(stateMarker.cities.length>0){
返回(
);
}否则{
返回(
);
}
}
这取决于您拥有的标记的数量。如果标记的数量不太多,可以全部重新命名。但是如果标记太多(数百或数千个)它们占用大量内存,因为它们需要许多dom元素来绘制,这对浏览器来说非常昂贵。提供一个小的演示来重现这个问题会很好。@kboul自己很难制作一个演示,但标记肯定在一个屏幕上有数百个。我见过很多人在一个m上绘制数万个数据点的演示ap没有问题。这是为什么?标记图标有问题吗?我应该用更有效的图标替换图标吗?你可以使用圆形标记、画布选项或标记簇来渲染大量标记。查看更多详细信息我认为你的重新渲染问题是因为stateMarkers
是一个状态变量可以在LeafMap
上运行,因此当它发生变化时,整个地图都会重新渲染。我会将GetMarkers
分解到它自己的组件中,并将所有API和状态逻辑移动到该组件中,并且不会使地图的渲染以该状态变量为条件。无论发生什么情况,只要渲染GetMarkers
。IfstateMarker.cities。length
为0,这很好,它只会不渲染任何标记。至少这样,当api调用运行时,GetMarkers
会更新,但不一定会更新整个地图,这取决于您拥有的标记数。如果标记数不太多,则可以重新渲染所有标记。但如果标记数太多(数百或数千)它们占用大量内存,因为它们需要许多dom元素来绘制,这对浏览器来说非常昂贵。提供一个小的演示来重现这个问题会很好。@kboul自己很难制作一个演示,但标记肯定在一个屏幕上有数百个。我见过很多人在一个m上绘制数万个数据点的演示ap没有问题。这是为什么?标记图标有问题吗?我应该用更有效的图标替换图标吗?你可以使用圆形标记、画布选项或标记簇来渲染大量标记。查看更多详细信息我认为你的重新渲染问题是因为stateMarkers
是一个状态变量可以在LeafMap
上运行,因此当它更改时,整个映射将重新渲染。我会将GetMarkers
分解到它自己的组件中,并将所有API和状态逻辑移动到该组件中,并且不使映射的渲染以该状态变量为条件。只需渲染GetM即可
import React, { useEffect } from "react";
import { MapContainer, Marker, Popup, TileLayer, useMap, useMapEvents } from "react-leaflet";
import "../../css/app.css";
import { useSelector, useDispatch, batch } from "react-redux";
import { getCities, setEndpoint } from "../features/markerSlice";
import { setLatBnd, setLngBnd, setZoom, setLat, setLng } from "../features/mapSlice";
export const LeafMap = () => {
const dispatch = useDispatch();
//marker state (marker data)
const stateMarker = useSelector(state => state.marker);
// map state (center, zoom level, bounds, etc)
const stateMap = useSelector(state => state.map);
// This calls the API to retrieve data
useEffect(() => {
dispatch(getCities(stateMarker.endpoint));
}, [stateMarker.endpoint]);
// Custom Marker Component to render markers
const GetMarkers = () => {
const markerType = stateMarker.markerType;
return stateMarker.cities.map((el, i) => (
<Marker
key={i}
position={[el.latitude, el.longitude]}
>
</Marker>
));
};
// This is a child component to MapContainer (leaflet v3) which listens to map events
function GetMapProperties() {
const map = useMap();
// listen to drag event end
useMapEvents({
dragend: () => {
// Get map info in order to create endpoint parameters for API call
const bounds = map.getBounds();
const latBnd = bounds['_northEast'].lat
const lngBnd = bounds['_northEast'].lng
const zoom = map.getZoom();
const lat = map.getCenter().lat;
const lng = map.getCenter().lng;
// update endpoint which triggers API call (via useEffect on top)
dispatch(setEndpoint({type:"trees", lat:lat, lng:lng, latbnd:latBnd, lngbnd:lngBnd}))
},
});
// render component based on if there is available data
if (stateMarker.cities.length > 0) {
return (
<MapContainer preferCanvas={true} center={[stateMap.lat, stateMap.lng]} zoom={stateMap.zoom} scrollWheelZoom={true}>
<GetMapProperties />
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<GetMarkers />
</MapContainer>
);
} else {
return (
<MapContainer center={[stateMap.lat, stateMap.lng]} zoom={stateMap.zoom} scrollWheelZoom={true}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
);
}
}