超时仍然在清除超时并卸载组件后继续触发和执行

huangapple go评论70阅读模式
英文:

Timouts continue to fire and execute after clearing timeouts and unmounting component

问题

I understand that you want a translation of the code and the description you provided. Here's the translated code:

我正在尝试从坐标数组创建公交车动画我使用setTimeout来触发将标记移动到下一个坐标的函数但当用户从父组件中选择另一个站点时我需要停止执行因此当用户单击另一个站点时我首先将路线设置为Null并获取该特定站点的公交车的新坐标该站点在下面的代码中表示路线

问题
即使清除了超时并卸载了组件函数仍然继续触发
我不知道我做错了什么
以下是我的代码

公交车标记组件

```javascript
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.log('animation start for marker', 
      props.route.bus_number, 'from position :', 
      current);

    props?.route?.route?.slice(current + 1)?.forEach((bus, index) => {
      timerRef.current = setTimeout(() => {
        console.log('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.log('clearAllTimeouts');
    timeouts.forEach(timeout => {
      clearTimeout(timeout);
    });
    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(); // 获取所选站点的公交车新坐标
}, [selectedStation]);

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

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

请注意,我已经将代码部分翻译成中文,但我没有翻译代码中的变量名和函数名,以保持代码的完整性。如果您需要更多帮助或有其他问题,请随时提出。
<details>
<summary>英文:</summary>
I&#39;m trying to create a bus animations from an array of coordinates, i&#39;m using setTimeout to fire function to move marker to next coordinate, but i need to stop execution when user chooses another station from parent component, so when user clicks in another station, i firstly set routes to Null and fetch new coordinates for the buses of this particular station, which represents routes in the code below
The problem : 
functions continue firing even after clearing timeouts and unmounting component.
I don&#39;t know what i&#39;m doing wrong
here is my code
Bus marker component :

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.log('animation start for marker',
props.route.bus_number, 'from position :',
current);

props?.route?.route?.slice(current + 1)?.forEach((bus, index) =&gt; {
timerRef.current = setTimeout(() =&gt; {
console.log(&#39;animateMarker&#39;, props.route.bus_number, &#39;to position index:&#39;, 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.log('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} />}
/>
)
);
});

and this is the parent component :

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

const handleChangeStation = (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


</details>
# 答案1
**得分**: 2
```js
React.useEffect(() => {
return () => {
clearAllTimeouts();
};
}, [props.selectedStation, props.routes]);
更改超时数组不会导致上面的效果更改该效果实际上会清除超时注意clearAllTimeouts正在使用数组)。
因此存储在setTimeOuts中的超时在未更改selectedStation或routes的情况下永远不会被取消

通常您可以简化您的代码并将所有与动画相关的代码放入一个React.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) {
      /* 没有要动画的路线*/
      return;
    }

    console.log('animation start for marker', props.route.bus_number, 'from position :', currentLocation);
    const timeouts = currentRoute.route.slice(currentLocation + 1).map((bus, index) => {
      return setTimeout(() => {
        console.log('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 () => {
      /* 当动画参数更改或卸载时清除所有超时。 */
      for (const timeout of timeouts) {
        clearTimeout(timeout);
      }
    };
  }, [currentRoute, props.selectedStation, currentLocation]);
  /* 为什么之前selectedStation已经成为依赖关系? */

  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的依赖项数组中。
请注意,我尚未测试过此代码,因此可能存在一些拼写错误或我可能忽略了某些内容。

英文:
React.useEffect(() =&gt; {
 return () =&gt; {
   clearAllTimeouts();
 };
}, [props.selectedStation, props.routes]);

Changing the array of timeouts does not cause the effect from above to change which actually clears the timeouts (note: clearAllTimeouts is using the "old" array).
Therefore timeouts which are stored in setTimeOuts without changing ether selectedStation or routes the new timeouts are never cancelled.

In general you can simplify your code a lot and putting all animation consern related code into one useEffect:

export const BusMarker = React.memo(function BusMarker(props: Props) {
    const name = props.route?.bus_number?.replace(/\D/g, &#39;&#39;);
    const busRef = React.useRef&lt;Marker&gt;(null);

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

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

        return () =&gt; {
            /* 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] &amp;&amp; (
            &lt;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={&lt;BusView name={name} type={props.route?.tt_id} /&gt;}
            /&gt;
        )
    );
});

I'd recommand putting any dependencies which causes the animation to change into the React.useEffect dependency array.
Please note that I haven't tested this code so there might be some typos or I overlooked something.

huangapple
  • 本文由 发表于 2023年2月18日 15:47:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/75491920.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定