英文:
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'm trying to create a bus animations from an array of coordinates, i'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't know what i'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) => {
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.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(() => {
return () => {
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, '');
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.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 () => {
/* 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} />}
/>
)
);
});
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论