Тайм-ауты продолжают срабатывать и выполняться после очистки тайм-аутов и размонтирования компонента.

Я пытаюсь создать анимацию автобуса из массива координат, я использую функцию setTimeout для запуска, чтобы переместить маркер на следующую координату, но мне нужно остановить выполнение, когда пользователь выбирает другую станцию ​​из родительского компонента, поэтому, когда пользователь нажимает другой станции, я сначала устанавливаю маршруты на Null и получаю новые координаты для автобусов этой конкретной станции, которые представляют маршруты в коде ниже

Проблема : функции продолжают работать даже после очистки тайм-аутов и размонтирования компонента. я не знаю, что я делаю неправильно вот мой код

Компонент маркера шины:


import React from 'react';
import { Marker } from 'react-native-yamap';
import { BusView } from '../../../../../../components/transport/bus-view';
import { Route } from '../../../../../../modules/transport/types/Route';
import { Station } from 'modules/transport/types/Station';

interface Props {
  route: Route;
  visible: boolean;
  selectedStation: Station | null;
  routes: Route[];
}

export const BusMarker = React.memo(function BusMarker(props: Props) {
  const name = props.route?.bus_number?.replace(/\D/g, '');
  const busRef = React.useRef<Marker>(null);
  const [timeouts, setTimeOuts] = React.useState<NodeJS.Timeout[]>([]);
  let timerRef = React.createRef();
  
const animateMarker = React.useCallback(() => {
    const current = props?.route.current_location ?? 0;
    let prevTimeOuts: NodeJS.Timeout[] = [];
    console.info('animation start for marker', 
    props.route.bus_number, 'from position :', 
    current);

    props?.route?.route?.slice(current + 1)?.forEach((bus, index) => {
      timerRef.current = setTimeout(() => {
        console.info('animateMarker', props.route.bus_number, 'to position index:', index);
        busRef?.current?.animatedMoveTo(
          {
            lat: bus.lat,
            lon: bus.lon,
          },
          1000 * props.route.average_second_per_coordinate,
        );
      }, index * 1000 * props.route.average_second_per_coordinate);
      prevTimeOuts = [...prevTimeOuts, timerRef.current];
    });
    setTimeOuts(prevTimeOuts);
  }, [props.selectedStation]);

  const clearAllTimeouts = React.useCallback(() => {
    console.info('clearAllTimeouts');
    timeouts.forEach(timeout => {
      clearTimeout(timeout.current);
    });
    setTimeOuts([]);
  }, [timeouts]);

  React.useEffect(() => {
    animateMarker();
  }, []);

  React.useEffect(() => {
    return () => {
      clearAllTimeouts();
    };
  }, [props.selectedStation, props.routes]);

  return (
    props.route?.route?.[0] && (
      <Marker
        visible = {props.visible}
        scale = {1}
        ref = {busRef}
        key = {props.route.bus_number}
        point = {{
          lat: props.route?.route[props.route.current_location ?? 0].lat,
          lon: props.route?.route[props.route.current_location ?? 0].lon,
        }}
        children = {<BusView name = {name} type = {props.route?.tt_id} />}
      />
    )
  );
});

И это родительский компонент:

 React.useEffect(() => {
    getRoutes(); // fetch new coordinates for buses of selected station
  }, [selectedStation]);

const handleChangeEventStation = (station: Station) => {
    setRoutes(() => null);
    setSelectedStation(() => station);
  };

{routes?.[0] &&
   routes.map((route, index) => (
      <BusMarker 
         key = {index} 
         route = {route} 
         visible = {showBuses} 
         selectedStation = {selectedStation} 
         routes = {routes} 
      />
 ))} // render buses on map

Какой смысл в timerRef, если вы храните свои (несколько!) идентификаторов таймеров в массиве?

Bergi 18.02.2023 09:24

Я пробовал с ref или без него, и у меня тот же результат, я узнал, что должен сохранять ссылку на тайм-аут, потому что каждый раз, когда компонент перерисовывает, он создает новый

Zero0 18.02.2023 09:52

Да, было бы неплохо использовать здесь ссылку вместо состояния, но вам нужно хранить все ваши идентификаторы тайм-аута, которые вы создаете в этом цикле forEach, там. Не только последний.

Bergi 18.02.2023 09:55
const timerId = setTimeout(() => { console.info('animateMarker', props.route.bus_number, 'to position index:', index); busRef?.current?.animatedMoveTo( { lat: bus.lat, lon: bus.lon, }, 1000 * props.route.average_second_per_coordinate, ); }, index * 1000 * props.route.average_second_per_coordinate); prevTimeOuts = [...prevTimeOuts, timerId]; }); setTimeOuts(prevTimeOuts);это без ссылки
Zero0 18.02.2023 09:58

Но результат тот же, это не останавливает выполнение

Zero0 18.02.2023 09:59

Как я уже сказал, удалите состояние timeouts. Используйте timersRef.current = prevTimeouts вместо setTimeOuts(prevTimeOuts);.

Bergi 19.02.2023 00:57
Laravel с Turbo JS
Laravel с Turbo JS
Turbo - это библиотека JavaScript для упрощения создания быстрых и высокоинтерактивных веб-приложений. Она работает с помощью техники под названием...
Слишком много useState? Давайте useReducer!
Слишком много useState? Давайте useReducer!
Современный фронтенд похож на старую добрую веб-разработку, но с одной загвоздкой: страница в браузере так же сложна, как и бэкенд.
Типы данных JavaScript
Типы данных JavaScript
В JavaScript существует несколько типов данных, включая примитивные типы данных и ссылочные типы данных. Вот краткое объяснение различных типов данных...
CSS Flex: что должен знать каждый разработчик
CSS Flex: что должен знать каждый разработчик
CSS Flex: что должен знать каждый разработчик Модуль flexbox, также известный как гибкий модуль разметки box, помогает эффективно проектировать и...
Введение в раздел &quot;Заголовок&quot; в HTML
Введение в раздел "Заголовок" в HTML
Говорят, что лучшее о человеке можно увидеть только изнутри, и это относится и к веб-страницам HTML! Причина, по которой некоторые веб-страницы не...
Как React Helmet спасает меня при разделении файлов CSS?
Как React Helmet спасает меня при разделении файлов CSS?
Многие новички могут столкнуться с проблемой, когда одна страница с CSS наследует свойства от другой страницы с другим CSS. У меня было много проблем,...
4
6
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
React.useEffect(() => {
 return () => {
   clearAllTimeouts();
 };
}, [props.selectedStation, props.routes]);

Изменение массива тайм-аутов не приводит к изменению эффекта сверху, который фактически очищает тайм-ауты (примечание: clearAllTimeouts использует «старый» массив). Поэтому тайм-ауты, которые сохраняются в setTimeOuts без изменения эфира selectedStation или маршрутизации новых тайм-аутов, никогда не отменяются.

В общем, вы можете значительно упростить свой код и поместить весь код, связанный с анимацией, в один useEffect:

export const BusMarker = React.memo(function BusMarker(props: Props) {
    const name = props.route?.bus_number?.replace(/\D/g, '');
    const busRef = React.useRef<Marker>(null);

    const currentRoute = props.route;
    const currentLocation = props.route.current_location ?? 0;
    React.useEffect(() => {
        if (!currentRoute || !currentRoute.route || currentRoute.route.length === 0) {
            /* No route to animate*/
            return;
        }

        console.info('animation start for marker', props.route.bus_number, 'from position :', currentLocation);
        const timeouts = currentRoute.route.slice(currentLocation + 1).map((bus, index) => {
            return setTimeout(() => {
                console.info('animateMarker', currentRoute.bus_number, 'to position index:', index);
                busRef.current?.animatedMoveTo(
                    {
                        lat: bus.lat,
                        lon: bus.lon,
                    },
                    1000 * currentRoute.average_second_per_coordinate,
                );
            }, index * 1000 * currentRoute.average_second_per_coordinate);
        });

        return () => {
            /* Cleanup all timeouts when animation parameters changed or on unmount. */
            for (const timeout of timeouts) {
                clearTimeout(timeout);
            }
        };
    }, [currentRoute, props.selectedStation, currentLocation]);
    /* Does props.selectedStation equal currentLocation or why did selectedStation has been a dependency beforehand? */

    return (
        props.route?.route?.[0] && (
            <Marker
                visible = {props.visible}
                scale = {1}
                ref = {busRef}
                key = {props.route.bus_number}
                point = {{
                    lat: props.route?.route[props.route.current_location ?? 0].lat,
                    lon: props.route?.route[props.route.current_location ?? 0].lon,
                }}
                children = {<BusView name = {name} type = {props.route?.tt_id} />}
            />
        )
    );
});

Я бы рекомендовал поместить любые зависимости, которые вызывают изменение анимации, в массив зависимостей React.useEffect. Обратите внимание, что я не тестировал этот код, поэтому могут быть опечатки или я что-то упустил.

Могу ли я заменить карту на forEach?

Zero0 20.02.2023 11:24

Нет, так как мат возвращает идентификатор тайм-аута, который затем сохраняется как «тайм-ауты». Вместо этого вы можете объявить массив как const timeouts = [], использовать forEach и вставить идентификатор тайм-аута в тайм-ауты.

WolverinDEV 20.02.2023 12:49

Другие вопросы по теме