React Native Maps在onRegionChangeComplete重取数据时崩溃问题
解决React Native地图标记渲染与性能问题
问题核心
滚动更新地图时触发handleRegionChange,遇到两个矛盾问题:
- 不加
if (!data?.length) return null:首次无数据不显示标记(符合预期),但数据返回后标记仍不渲染,滚动操作还会导致Expo崩溃 - 加了该判断:每次操作地图都会触发整个组件重渲染,标记加载时体验卡顿,抽离标记组件也无法解决
原因分析
- 无判断时崩溃/不渲染:滚动时
mapViewRegion频繁更新,导致trpc的useQuery频繁发起请求,旧请求未完成新请求又触发,数据状态混乱甚至引发内存泄漏;同时频繁的状态切换可能导致React无法正确触发标记重渲染。 - 加判断后卡顿:每次滚动时
mapViewRegion变化,组件直接返回null,MapView被卸载,数据返回后又重新挂载,MapView初始化开销极大,导致卡顿。
解决方案
1. 保留MapView始终渲染,避免卸载重挂载
不要在根组件返回null,而是在标记渲染处处理空数据和加载状态,保持MapView一直存在,避免重复初始化的性能开销。
2. 防抖处理地图区域变化,减少请求频率
滚动时onRegionChangeComplete会频繁触发,导致mapViewRegion频繁更新,进而触发useQuery频繁请求。用防抖处理区域状态,只在用户停止滚动后才发起请求,避免请求堆积。
3. 抽离标记组件并使用React.memo优化
将标记渲染逻辑抽成独立组件,用React.memo包裹,只有当data变化时才重新渲染标记,避免MapView每次重渲染都重新生成标记。
修改后的代码示例
import { useDebounce } from 'use-debounce'; import React, { memo, useRef, useState } from 'react'; import { View, ActivityIndicator } from 'react-native'; import MapView, { Region, MapMarker } from 'react-native-maps'; import { LocationObject } from 'expo-location'; import { trpc } from '../utils/trpc'; // 抽离标记组件,用React.memo避免不必要重渲染 const MapMarkers = memo(({ data }: { data: Array<{ id: string; latitude: string; longitude: string; name: string; address: string }> }) => { return data.map((venue) => ( <MapMarker key={venue.id} identifier={venue.id.toString()} coordinate={{ latitude: Number(venue.latitude), longitude: Number(venue.longitude) }} title={venue.name} description={`${venue.address}`} /> )); }); function MapViewWithMarkers({ location }: { location: LocationObject }) { const mapRef = useRef<MapView>(null); const [mapViewRegion, setMapViewRegion] = useState<Region>({ latitude: location.coords.latitude, longitude: location.coords.longitude, latitudeDelta: 0.015, longitudeDelta: 0.015, }); // 防抖处理地图区域,300ms延迟,用户停止滚动后再更新查询参数 const [debouncedRegion] = useDebounce(mapViewRegion, 300); const handleRegionChange = (region: Region) => { setMapViewRegion(region); }; // 使用防抖后的区域作为查询依赖,减少请求次数 const { data, isLoading } = trpc.venues.useQuery( { lat: debouncedRegion.latitude, lon: debouncedRegion.longitude, limit: 10 }, { enabled: !!debouncedRegion } ); return ( <View style={{ flex: 1 }}> <MapView ref={mapRef} showsUserLocation={true} region={mapViewRegion} showsPointsOfInterest={false} onRegionChangeComplete={handleRegionChange} style={{ width: "100%", height: "100%", flex: 1 }} loadingEnabled={true} showsMyLocationButton={true} > {/* 加载状态显示指示器,空数据时不渲染标记 */} {isLoading ? ( <MapMarker coordinate={mapViewRegion}> <ActivityIndicator size="small" color="#000" /> </MapMarker> ) : data?.length ? ( <MapMarkers data={data} /> ) : null} </MapView> </View> ); } export default MapViewWithMarkers;
额外优化点
- 若不想安装
use-debounce,可自行用useEffect+setTimeout实现防抖逻辑 - 给
useQuery添加staleTime参数,让缓存数据在一定时间内有效,避免重复请求相同区域数据
内容的提问来源于stack exchange,提问作者Jamie
相关产品推荐
相关产品推荐

