perf(tracks): bound playback rendering work
This commit is contained in:
@@ -9,8 +9,11 @@ const overlaySetMap = vi.fn();
|
||||
const mapOff = vi.fn();
|
||||
const mapDestroy = vi.fn();
|
||||
const markerListeners: Array<{ overlay: TestOverlay; event: string; handler: () => void }> = [];
|
||||
const overlayInstances: TestOverlay[] = [];
|
||||
const mapInstances: TestMap[] = [];
|
||||
|
||||
class TestOverlay {
|
||||
constructor() { overlayInstances.push(this); }
|
||||
on = vi.fn((event: string, handler: () => void) => markerListeners.push({ overlay: this, event, handler }));
|
||||
off = overlayOff;
|
||||
setMap = overlaySetMap;
|
||||
@@ -19,6 +22,7 @@ class TestOverlay {
|
||||
}
|
||||
|
||||
class TestMap {
|
||||
constructor() { mapInstances.push(this); }
|
||||
add = vi.fn();
|
||||
addControl = vi.fn();
|
||||
setFitView = vi.fn();
|
||||
@@ -90,6 +94,38 @@ afterEach(() => {
|
||||
mapOff.mockReset();
|
||||
mapDestroy.mockReset();
|
||||
markerListeners.length = 0;
|
||||
overlayInstances.length = 0;
|
||||
mapInstances.length = 0;
|
||||
});
|
||||
|
||||
test('uses the playback cadence for follow animation and advances the passed path', async () => {
|
||||
window.__LINGNIU_APP_CONFIG__ = { amapWebJsKey: 'amap-web-key' };
|
||||
window.AMapLoader = { load: vi.fn(async () => amapMock()) };
|
||||
const view = render(<TrackMap
|
||||
points={points}
|
||||
stops={[]}
|
||||
activeIndex={0}
|
||||
showStops={false}
|
||||
follow
|
||||
followDurationMs={65}
|
||||
onSelectIndex={() => undefined}
|
||||
onFollowChange={() => undefined}
|
||||
/>);
|
||||
|
||||
await waitFor(() => expect(mapInstances).toHaveLength(1));
|
||||
view.rerender(<TrackMap
|
||||
points={points}
|
||||
stops={[]}
|
||||
activeIndex={1}
|
||||
showStops={false}
|
||||
follow
|
||||
followDurationMs={65}
|
||||
onSelectIndex={() => undefined}
|
||||
onFollowChange={() => undefined}
|
||||
/>);
|
||||
|
||||
await waitFor(() => expect(mapInstances[0].panTo).toHaveBeenLastCalledWith(expect.any(Array), 65));
|
||||
expect(overlayInstances.some((overlay) => overlay.setPath.mock.calls.some(([path]) => Array.isArray(path) && path.length === 2))).toBe(true);
|
||||
});
|
||||
|
||||
test('detaches every marker listener and map listener on unmount', async () => {
|
||||
|
||||
@@ -8,12 +8,13 @@ function markerContent(kind: 'start' | 'end' | 'stop' | 'current', label?: strin
|
||||
return `<div class="v2-track-marker is-${kind}"><span>${label ?? ''}</span></div>`;
|
||||
}
|
||||
|
||||
export function TrackMap({ points, stops, activeIndex, showStops, follow, onSelectIndex, onFollowChange }: {
|
||||
export function TrackMap({ points, stops, activeIndex, showStops, follow, followDurationMs = 180, onSelectIndex, onFollowChange }: {
|
||||
points: HistoryLocationRow[];
|
||||
stops: TrackStop[];
|
||||
activeIndex: number;
|
||||
showStops: boolean;
|
||||
follow: boolean;
|
||||
followDurationMs?: number;
|
||||
onSelectIndex: (index: number) => void;
|
||||
onFollowChange: (follow: boolean) => void;
|
||||
}) {
|
||||
@@ -29,6 +30,15 @@ export function TrackMap({ points, stops, activeIndex, showStops, follow, onSele
|
||||
const followChangeRef = useRef(onFollowChange);
|
||||
const [state, setState] = useState<'loading' | 'ready' | 'fallback' | 'error'>('loading');
|
||||
const valid = useMemo(() => points.map((point, index) => ({ point, index })).filter(({ point }) => isValidAMapCoordinate(point.longitude, point.latitude)), [points]);
|
||||
const passedCountByPoint = useMemo(() => {
|
||||
const counts = new Uint16Array(points.length);
|
||||
let validIndex = 0;
|
||||
for (let pointIndex = 0; pointIndex < points.length; pointIndex += 1) {
|
||||
while (validIndex < valid.length && valid[validIndex].index <= pointIndex) validIndex += 1;
|
||||
counts[pointIndex] = validIndex;
|
||||
}
|
||||
return counts;
|
||||
}, [points.length, valid]);
|
||||
|
||||
useEffect(() => { selectRef.current = onSelectIndex; }, [onSelectIndex]);
|
||||
useEffect(() => { followChangeRef.current = onFollowChange; }, [onFollowChange]);
|
||||
@@ -127,10 +137,10 @@ export function TrackMap({ points, stops, activeIndex, showStops, follow, onSele
|
||||
if (!point || !isValidAMapCoordinate(point.longitude, point.latitude)) return;
|
||||
const position = wgs84ToGcj02(point.longitude, point.latitude);
|
||||
currentMarkerRef.current?.setPosition?.(position);
|
||||
const passedCount = valid.reduce((count, entry) => count + (entry.index <= activeIndex ? 1 : 0), 0);
|
||||
const passedCount = passedCountByPoint[activeIndex] ?? 0;
|
||||
passedPathRef.current?.setPath?.(pathRef.current.slice(0, Math.max(1, passedCount)));
|
||||
if (follow) mapRef.current?.panTo?.(position, 180);
|
||||
}, [activeIndex, follow, points, valid]);
|
||||
if (follow) mapRef.current?.panTo?.(position, followDurationMs);
|
||||
}, [activeIndex, follow, followDurationMs, passedCountByPoint, points]);
|
||||
|
||||
return <div className="v2-track-map">
|
||||
<div ref={containerRef} className="v2-track-map-canvas" aria-label="历史轨迹地图" />
|
||||
|
||||
Reference in New Issue
Block a user