ai-coding
2026-06-30 ff53131dd811c378f351ea42379bae052918e05d
feat: 优化地图 rerender

Co-Authored-By: 卢春龙 <lucl@xiaopeng.com>
1个文件已添加
1个文件已修改
113 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/begin-route-map-canvas.tsx 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-view.tsx 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-map-canvas.tsx
New file
@@ -0,0 +1,74 @@
import { Map } from '@tarojs/components';
import { memo } from 'react';
import type { ReactNode } from 'react';
type BeginRouteMapCanvasProps = {
  /** 地图中心经度。 */
  longitude: number;
  /** 地图中心纬度。 */
  latitude: number;
  /** 地图缩放等级。 */
  scale: number;
  /** 地图标记点。 */
  markers: any[];
  /** 地图折线。 */
  polyline: any[];
  /** 是否展示定位按钮。 */
  showLocation: boolean;
  /** marker 气泡点击(需保持引用稳定)。 */
  onCalloutTap: (e: any) => void;
  /** 地图区域变化(需保持引用稳定)。 */
  onRegionChange: (e: any) => void;
  /** 折线点击(需保持引用稳定)。 */
  onPolylineTap?: (e: any) => void;
  /** 地图内覆盖层(覆盖物 / 气泡),由上层以 children 形式传入。 */
  children?: ReactNode;
};
/**
 * 地图画布包装组件。
 *
 * 受控 <Map longitude/latitude/scale> 在每次元素更新时都会把相机重新写回原生地图;行程中
 * 大量无关状态(计时器、弹窗等)会触发页面重渲染,若 <Map> 跟着重渲染,就会把受控相机重新下发,
 * 与用户手势/原生视口产生争抢,表现为拖动被弹回、视口抖动。
 *
 * 这里用 React.memo 包裹:仅当地图相关属性(相机 / markers / polyline / showLocation /
 * 事件回调 / children)发生变化时才重渲染,无关重渲染一律跳过,<Map> 不再被反复触碰。
 *
 * 注意:使用方需保证传入的函数属性引用稳定、children 仅在覆盖物真正变化时换新,
 * 否则浅比较会被打穿、memo 失效。
 */
function BeginRouteMapCanvas(props: BeginRouteMapCanvasProps) {
  const {
    longitude,
    latitude,
    scale,
    markers,
    polyline,
    showLocation,
    onCalloutTap,
    onRegionChange,
    onPolylineTap,
    children,
  } = props;
  return (
    <Map
      id='map'
      longitude={longitude}
      latitude={latitude}
      scale={scale}
      markers={markers}
      polyline={polyline}
      showLocation={showLocation}
      onCalloutTap={onCalloutTap}
      onRegionChange={onRegionChange}
      onPolylineTap={onPolylineTap}
      style={{ width: '100%', height: '87vh' }}
    >
      {children}
    </Map>
  );
}
export default memo(BeginRouteMapCanvas);
src/robotaxi/pages/begin-route/components/begin-route-view.tsx
@@ -1,4 +1,5 @@
import { View, Map, Image } from '@tarojs/components';
import { View, Image } from '@tarojs/components';
import { useCallback, useMemo, useRef } from 'react';
import CustomerServicePopup from '../../../components/customer-service';
import CancelPopup from './cancel-popup';
import AnnouncementsPopup from './announcements-popup';
@@ -9,6 +10,7 @@
import AuthBluetoothPopup from './auth-bluetooth-popup';
import PrivacyPopup from '../../../../pages/robotaxi/home/components/privacy-popup';
import NoRoutePopup from './no-route-popup';
import BeginRouteMapCanvas from './begin-route-map-canvas';
import { useBeginRouteViewContext } from './begin-route-view-context';
/**
@@ -30,24 +32,41 @@
    renderCarCalloutOverlay,
  } = useBeginRouteViewContext();
  // 保持传给 BeginRouteMapCanvas 的函数属性引用稳定:上层这些 handler 每次渲染可能是新函数,
  // 直接透传会让 memo 的浅比较失效。用 ref 存最新实现,对外暴露引用恒定的包装函数。
  const handleCalloutTapRef = useRef(handleCalloutTap);
  handleCalloutTapRef.current = handleCalloutTap;
  const handleRegionChangeRef = useRef(handleRegionChange);
  handleRegionChangeRef.current = handleRegionChange;
  const onPolylineTapRef = useRef(onPolylineTap);
  onPolylineTapRef.current = onPolylineTap;
  const stableCalloutTap = useCallback((e: any) => handleCalloutTapRef.current?.(e), []);
  const stableRegionChange = useCallback((e: any) => handleRegionChangeRef.current?.(e), []);
  const stablePolylineTap = useCallback((e: any) => onPolylineTapRef.current?.(e), []);
  // 覆盖物作为 children 透传:renderMapOverlays 是 useCallback,仅在其依赖(覆盖物相关状态)变化时
  // 换引用,据此 memo 出 overlays 元素,避免无关重渲染产生新元素打穿 BeginRouteMapCanvas 的 memo。
  const overlays = useMemo(() => renderMapOverlays(), [renderMapOverlays]);
  return (
    <>
      <Map
        id='map'
      {/* 地图画布包装组件:用 React.memo 收敛 <Map> 的重渲染,仅地图相关属性变化时才更新,
          避免行程中无关状态重渲染反复把受控相机写回原生地图导致拖动被弹回 / 视口抖动。
          覆盖物以 children 形式放入,写法与原来一致,跟车 overlay 照常随状态更新。 */}
      <BeginRouteMapCanvas
        longitude={mapLongitude}
        latitude={mapLatitude}
        scale={mapScale}
        markers={markers}
        polyline={polyline}
        showLocation={showLocation}
        onCalloutTap={handleCalloutTap}
        onRegionChange={handleRegionChange}
        onPolylineTap={onPolylineTap}
        // onTap={onMapTap}
        style={{ width: '100%', height: '87vh' }}
        onCalloutTap={stableCalloutTap}
        onRegionChange={stableRegionChange}
        onPolylineTap={stablePolylineTap}
      >
        {renderMapOverlays()}
      </Map>
        {overlays}
      </BeginRouteMapCanvas>
      {/* iOS 里程气泡:作为 Map 的兄弟级普通 View 浮层,靠同层渲染浮在地图上,z-index 低于底部面板。 */}
      {renderCarCalloutOverlay()}
    </>