#javascript #reactjs
Вопрос:
Как я могу очистить функцию, подобную setTimeout
или setInterval
в обработчике событий в React? Или в этом нет необходимости?
import React from 'react'
function App(){
return (
<button onClick={() => {
setTimeout(() => {
console.log('you have clicked me')
//How to clean this up?
}, 500)
}}>Click me</button>
)
}
export default App
Ответ №1:
Необходимо ли это, зависит от того, что делает обратный вызов, но, конечно, если компонент размонтирован, почти не имеет значения, что он делает, вам нужно отменить таймер / очистить интервал.
Чтобы сделать это в функциональном компоненте, подобном вашему, вы используете useEffect
функцию очистки с пустым массивом зависимостей. Вероятно, вы хотите сохранить дескриптор таймера в a ref
.
(FWIW, я бы также определил функцию вне onClick
атрибута, просто для ясности.)
import React, {useEffect, useRef} from 'react';
function App() {
const instance = useRef({timer: 0});
useEffect(() => {
// What you return is the cleanup function
return () => {
clearTimeout(instance.current.timer);
};
}, []);
const onClick = () => {
// Clear any previous one (it's fine if it's `0`,
// `clearTimeout` won't do anything)
clearTimeout(instance.current.timer);
// Set the timeout and remember the value on the object
instance.current.timer = setTimeout(() => {
console.log('you have clicked me')
//How to clean this up?
}, 500);
};
return (
<button onClick={onClick}>Click me</button>
)
}
export default App;
Объект, который вы храните в качестве ссылки, обычно является полезным местом для размещения вещей, которые вы в противном случае поместили this
бы в компонент класса.
(Если вы хотите избежать повторного рендеринга button
при изменении другого состояния в вашем компоненте (сейчас другого состояния нет, так что в этом нет необходимости), вы можете использовать useCallback
для onClick
того, чтобы button
всегда видеть одну и ту же функцию.)
Комментарии:
1. Привет, Т. Джей, есть ли преимущества хранения идентификатора таймера в качестве ссылки вместо обычной переменной?
2. @AmirSaleem — Вы имеете в виду локальную переменную в функции? Да, это не будет работать надежно. 🙂 Функция вызывается в любое время, когда React необходимо повторно отобразить компонент, и локальная переменная воссоздается каждый раз при вызове функции, в то время как экземпляр from
useRef
стабилен. В локальном случае, если компонент был повторно отрисован хотя бы один раз до нажатия кнопки,useEffect
обратный вызов завершится над устаревшей копией переменной, а не той, которую устанавливает кнопка. (ПосколькуuseEffect
из-за пустого массива зависимостей используется только первая версия обратного вызова.)3. (Ой, в ответе выше была опечатка. Если вы видите
instance.timer
, а неinstance.current.timer
, нажмите обновить.)4. Спасибо Ти Джею за ваше подробное объяснение. Это было проницательно
Ответ №2:
Еще одно решение (Живая демонстрация):
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
export default function TestComponent(props) {
const [text, setText] = useState("");
const click = useAsyncCallback(function* (ms) {
yield CPromise.delay(ms);
setText("done!" new Date().toLocaleTimeString());
}, []);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={() => click(2000)}>Click me!</button>
<button onClick={click.cancel}>Cancel scheduled task</button>
</div>
);
}
В случае, если вы хотите отменить предыдущую отложенную задачу (демонстрационная версия):
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
export default function TestComponent(props) {
const [text, setText] = useState("");
const click = useAsyncCallback(
function* (ms) {
console.log("click");
yield CPromise.delay(ms);
setText("done!" new Date().toLocaleTimeString());
},
{ deps: [], cancelPrevios: true }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={() => click(5000)}>Click me!</button>
<button onClick={click.cancel}>Cancel scheduled task</button>
</div>
);
}
Ответ №3:
Снимите таймер при отключении компонента
import React from 'react'
function App(){
const timerRef = React.useRef(null)
React.useEffect(() => {
return () => {
// clean
timerRef.target amp;amp; clearTimeout(timerRef.target)
}
},[])
return (
<button onClick={() => {
timerRef.target = setTimeout(() => {
console.log('you have clicked me')
}, 500)
}}>Click me</button>
)
}
export default App
Комментарии:
1.Две проблемы с изложенным: 1. Каждый экземпляр
App
будет делиться жеtimer
переменной, которая является большой нет-нет. 🙂 2. Ты упускаешь пустой массив зависимостей наuseEffect
звонок, поэтому он будет вызывать функцию очистки, что каждый раз, когда компонент будет повторно визуализироваться, а не только тогда, когда компонент размонтируется.2. Спасибо за ваш комментарий, мой плохой, я все исправил.
3.
.target
должно быть.current
в вашей последней версии.