fix(map): 패널 선택 fly-to 즉시 반응 개선

- shipData를 ref로 관리하여 AIS poll마다 effect 재실행 방지
- isStyleLoaded 가드 제거 → try/catch로 즉시 실행
- duration 700→400ms로 단축
- selectedMmsi만 의존성으로 → 선택 시 1회만 fly-to 실행

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
htlee 2026-02-17 16:47:38 +09:00
부모 f9da13b694
커밋 59a5e6beac

파일 보기

@ -1,6 +1,5 @@
import { useEffect, type MutableRefObject } from 'react';
import { useEffect, useRef, type MutableRefObject } from 'react';
import type maplibregl from 'maplibre-gl';
import { onMapStyleReady } from '../lib/mapCore';
import type { MapProjectionId } from '../types';
export function useFlyTo(
@ -19,7 +18,11 @@ export function useFlyTo(
) {
const { selectedMmsi, shipData, mapInitiatedSelectRef, fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom } = opts;
// 패널(좌측 목록)에서 선택 시 해당 선박 위치로 이동
// shipData를 ref로 — 의존성에서 제외하여 AIS poll마다 재실행 방지
const shipDataRef = useRef(shipData);
useEffect(() => { shipDataRef.current = shipData; }, [shipData]);
// 패널(좌측 목록)에서 선택 시 해당 선박 위치로 즉시 이동
useEffect(() => {
// 지도 내부 클릭에서 발생한 선택이면 스킵
if (mapInitiatedSelectRef.current) {
@ -30,53 +33,37 @@ export function useFlyTo(
const map = mapRef.current;
if (!map || selectedMmsi == null) return;
const target = shipData.find((t) => t.mmsi === selectedMmsi);
const target = shipDataRef.current.find((t) => t.mmsi === selectedMmsi);
if (!target || !Number.isFinite(target.lon) || !Number.isFinite(target.lat)) return;
const apply = () => {
const flyOpts = { center: [target.lon, target.lat] as [number, number], duration: 700 };
try {
const flyOpts = { center: [target.lon, target.lat] as [number, number], duration: 400 };
if (projectionRef.current === 'globe') {
map.flyTo(flyOpts);
} else {
map.easeTo(flyOpts);
}
};
if (map.isStyleLoaded()) {
apply();
return;
} catch {
// ignore — style not ready 등
}
const stop = onMapStyleReady(map, apply);
return () => { stop(); };
}, [selectedMmsi, shipData]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedMmsi]);
// 선단 포커스 이동
useEffect(() => {
const map = mapRef.current;
if (!map || fleetFocusLon == null || fleetFocusLat == null || !Number.isFinite(fleetFocusLon) || !Number.isFinite(fleetFocusLat))
return;
const lon = fleetFocusLon;
const lat = fleetFocusLat;
const zoom = fleetFocusZoom ?? 10;
const apply = () => {
const flyOpts = { center: [lon, lat] as [number, number], zoom, duration: 700 };
try {
const flyOpts = { center: [fleetFocusLon, fleetFocusLat] as [number, number], zoom: fleetFocusZoom ?? 10, duration: 500 };
if (projectionRef.current === 'globe') {
map.flyTo(flyOpts);
} else {
map.easeTo(flyOpts);
}
};
if (map.isStyleLoaded()) {
apply();
return;
} catch {
// ignore
}
const stop = onMapStyleReady(map, apply);
return () => {
stop();
};
}, [fleetFocusId, fleetFocusLon, fleetFocusLat, fleetFocusZoom]);
}