lucl
2026-06-30 e50380863aa3e4aa45bc4270e19bfef267dfa4a1
Revert "fix(map): 手势期间冻结 <Map>"

This reverts commit 6c09357222ad9fe8ae3d11d1e9862ce199521583.
1个文件已修改
82 ■■■■■ 已修改文件
src/robotaxi/pages/begin-route/components/begin-route-map-canvas.tsx 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/robotaxi/pages/begin-route/components/begin-route-map-canvas.tsx
@@ -1,5 +1,5 @@
import { Map } from '@tarojs/components';
import { memo, useCallback, useRef } from 'react';
import { memo } from 'react';
import type { ReactNode } from 'react';
type BeginRouteMapCanvasProps = {
@@ -28,19 +28,15 @@
/**
 * 地图画布包装组件。
 *
 * 双层优化,分别解决两类重渲染:
 * 受控 <Map longitude/latitude/scale> 在每次元素更新时都会把相机重新写回原生地图;行程中
 * 大量无关状态(计时器、弹窗等)会触发页面重渲染,若 <Map> 跟着重渲染,就会把受控相机重新下发,
 * 与用户手势/原生视口产生争抢,表现为拖动被弹回、视口抖动。
 *
 * 1) React.memo(拦「无关」重渲染):行程中大量无关状态(计时器、弹窗等)会触发页面重渲染,
 *    只要传入属性(相机 / markers / polyline / showLocation / 事件回调 / children)未变,
 *    memo 就跳过,<Map> 不被触碰。要求上层保证函数属性引用稳定、children 仅在覆盖物真正变化时换新。
 * 这里用 React.memo 包裹:仅当地图相关属性(相机 / markers / polyline / showLocation /
 * 事件回调 / children)发生变化时才重渲染,无关重渲染一律跳过,<Map> 不再被反复触碰。
 *
 * 2) 手势期间冻结(拦「有关」重渲染):行程中车辆在动,markers / 覆盖物 / 路线距离持续变化,
 *    这些属性变化会打穿 memo 使 <Map> 重渲染;而受控 <Map longitude/latitude/scale> 每次更新都会
 *    把相机重新写回原生地图,在用户拖拽/缩放手势进行中就会和原生视口争抢,表现为「拖动被弹回」。
 *    因此在 regionchange 'begin'(用户手势)→'end' 之间复用同一个 <Map> 元素引用,命中 React 的
 *    same-element bailout,跳过该子树 reconcile,原生地图停留在用户操作后的位置。
 *    手势期间车标仍由命令式 translateMarker 原生动画推进(不依赖 markers 属性回灌),
 *    Android overlay 在 begin 时本就隐藏、end 再重算,故冻结不影响跟车与覆盖物表现。
 * 注意:使用方需保证传入的函数属性引用稳定、children 仅在覆盖物真正变化时换新,
 * 否则浅比较会被打穿、memo 失效。
 */
function BeginRouteMapCanvas(props: BeginRouteMapCanvasProps) {
  const {
@@ -56,51 +52,23 @@
    children,
  } = props;
  // 用户手势进行中标记:仅用户拖拽/缩放手势期间为 true,程序化视口调整(update)不置位。
  const interactingRef = useRef(false);
  // 缓存的 <Map> 元素引用:手势期间复用,命中 same-element bailout 避免相机被重新写回。
  const cachedMapRef = useRef<JSX.Element | null>(null);
  // 包裹上层 onRegionChange,在转发前维护手势进行中标记。
  // useCallback 依赖 onRegionChange(上层已稳定),保持引用稳定。
  const handleRegionChange = useCallback((event: any) => {
    const causedBy = String(event?.causedBy || event?.detail?.causedBy || '').toLowerCase();
    const eventType = String(event?.type || event?.detail?.type || '').toLowerCase();
    const isUserGesture = causedBy === 'gesture' || causedBy === 'drag' || causedBy === 'scale';
    if (eventType === 'begin') {
      if (isUserGesture) {
        interactingRef.current = true;
      }
    } else if (eventType === 'end') {
      // 上层 onRegionChange 会在 'end' 把原生实际 scale/center 回灌;先解锁,
      // 使 end 之后第一次重渲染按最新属性重建 <Map> 完成同步。
      interactingRef.current = false;
    }
    onRegionChange?.(event);
  }, [onRegionChange]);
  // 手势进行中且已有缓存:复用旧引用冻结相机;其余情况按最新属性重建元素。
  if (!interactingRef.current || !cachedMapRef.current) {
    cachedMapRef.current = (
      <Map
        id='map'
        longitude={longitude}
        latitude={latitude}
        scale={scale}
        markers={markers}
        polyline={polyline}
        showLocation={showLocation}
        onCalloutTap={onCalloutTap}
        onRegionChange={handleRegionChange}
        onPolylineTap={onPolylineTap}
        style={{ width: '100%', height: '87vh' }}
      >
        {children}
      </Map>
    );
  }
  return cachedMapRef.current;
  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);