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

React Native Maps在onRegionChangeComplete重取数据时崩溃问题

解决React Native地图标记渲染与性能问题

问题核心

滚动更新地图时触发handleRegionChange,遇到两个矛盾问题:

  • 不加if (!data?.length) return null:首次无数据不显示标记(符合预期),但数据返回后标记仍不渲染,滚动操作还会导致Expo崩溃
  • 加了该判断:每次操作地图都会触发整个组件重渲染,标记加载时体验卡顿,抽离标记组件也无法解决

原因分析

  1. 无判断时崩溃/不渲染:滚动时mapViewRegion频繁更新,导致trpc的useQuery频繁发起请求,旧请求未完成新请求又触发,数据状态混乱甚至引发内存泄漏;同时频繁的状态切换可能导致React无法正确触发标记重渲染。
  2. 加判断后卡顿:每次滚动时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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:16:04