Я не понимаю, почему, когда я использую функцию setTimeout, мой компонент реакции начинает бесконечный console.info. Все работает, но ПК начинает чертовски лагать.
Некоторые люди говорят, что эта функция по таймауту меняет мое состояние, и этот компонент перерисовки, который устанавливает новый таймер и так далее. Теперь мне нужно понять, как это правильно очистить.
export default function Loading() {
// if data fetching is slow, after 1 sec i will show some loading animation
const [showLoading, setShowLoading] = useState(true)
let timer1 = setTimeout(() => setShowLoading(true), 1000)
console.info('this message will render every second')
return 1
}
Очистить в другой версии кода не помогает:
const [showLoading, setShowLoading] = useState(true)
let timer1 = setTimeout(() => setShowLoading(true), 1000)
useEffect(
() => {
return () => {
clearTimeout(timer1)
}
},
[showLoading]
)
@ Think-Twice useState - это предлагаемое обновление для API ReactJS
@MarkC. Спасибо, я не знал об этом, так как в настоящее время не работаю над реакцией. Я думаю, что OP должен использовать setTimeout, чем использовать setInterval для отображения загрузчика
я смог сократить свой код.
@RTWTMI попробуйте использовать метод setTimeout вместо setInterval. потому что то, что происходит в вашем коде, заключается в том, что триггеры setInterval каждую секунду вы выполняете setState каждую секунду, чего вы не должны делать в ответ, и поэтому вы получаете эту ошибку
@ Think-Twice та же проблема. Некоторые парни грустят, что состояние обновления таймера и этот компонент повторно рендерится, а затем снова. Теперь думаю, что делать, чтобы этого не допустить: /
очистить таймер, когда компонент отключается, например clearTimer (timer1);
@ Think-Twice у меня получилось вот так, та же проблема: const [showLoading, setShowLoading] = useState (true) let timer1 = setTimeout (() => setShowLoading (true), 1000) useEffect (() => {return () => {clearTimeout (timer1)}}, [showLoading])



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Определенная функция return () => { /*code/* } внутри useEffect запускается каждый раз при запуске useEffect (кроме первого рендеринга при монтировании компонента) и при размонтировании компонента (если вы больше не отображаете компонент).
import { useState, useEffect } from "react";
const delay = 5;
export default function App() {
const [show, setShow] = useState(false);
useEffect(
() => {
let timer1 = setTimeout(() => setShow(true), delay * 1000);
// this will clear Timeout
// when component unmount like in willComponentUnmount
// and show will not change to true
return () => {
clearTimeout(timer1);
};
},
// useEffect will run only one time with empty []
// if you pass a value to array,
// like this - [data]
// than clearTimeout will run every time
// this value changes (useEffect re-run)
[]
);
return show ? (
<div>show is true, {delay}seconds passed</div>
) : (
<div>show is false, wait {delay}seconds</div>
);
}
import { useState, useEffect, useRef } from "react";
const delay = 1;
export default function App() {
const [counter, setCounter] = useState(0);
const timer = useRef(null); // we can save timer in useRef and pass it to child
useEffect(() => {
// useRef value stored in .current property
timer.current = setInterval(() => setCounter((v) => v + 1), delay * 1000);
// clear on component unmount
return () => {
clearInterval(timer.current);
};
}, []);
return (
<div>
<div>Interval is working, counter is: {counter}</div>
<Child counter = {counter} currentTimer = {timer.current} />
</div>
);
}
function Child({ counter, currentTimer }) {
// this will clearInterval in parent component after counter gets to 5
useEffect(() => {
if (counter < 5) return;
clearInterval(currentTimer);
}, [counter, currentTimer]);
return null;
}
Что делать, если вам нужно сбросить таймер как «при размонтировании», так и при изменении какого-либо состояния? Вы бы настроили два перехватчика: один с пустым массивом, а другой с соответствующей переменной состояния?
@loopmode, я думаю, вы можете просто добавить clearTimeout (timer1) в код, где изменяется состояние, но тогда вам нужно будет сохранить свой timer1 в переменной useState.
Нет ли риска состояния гонки? Я всегда проверяю, был ли вызван return in useEffect на всякий случай, прежде чем пытаться установить переменную состояния.
@raRaRar возвращает вызов при отключении компонента, о каком условии вы говорите?
Это было полезно, как и этот пост от самого Дэна Абрамова здесь overrected.io/making-setinterval-declarative-with-react-hoo ks, связанный с stackoverflow.com/a/59274757/470749 А вот версия useInterval на TypeScript: gist.github.com/Danziger/…
Ваш компьютер завис, потому что вы, вероятно, забыли передать пустой массив в качестве второго аргумента useEffect и запускали setState в обратном вызове. Это вызывает бесконечный цикл, потому что useEffect запускается при рендеринге.
Вот рабочий способ установить таймер при монтировании и сбросить его при размонтировании:
function App() {
React.useEffect(() => {
const timer = window.setInterval(() => {
console.info('1 second has passed');
}, 1000);
return () => { // Return callback to run on unmount.
window.clearInterval(timer);
};
}, []); // Pass in empty array to run useEffect only on mount.
return (
<div>
Timer Example
</div>
);
}
ReactDOM.render(
<div>
<App />
</div>,
document.querySelector("#app")
);<script src = "https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id = "app"></div>Как бы вы справились с очисткой тайм-аута, если вам нужно часто запускать эффект при изменении некоторого свойства, но запускать только один активный таймер и сбрасывать его при размонтировании?
Проблема в том, что вы вызываете setTimeout за пределами useEffect, поэтому вы устанавливаете новый тайм-аут каждый раз, когда компонент рендерится, который в конечном итоге будет вызываться снова и изменить состояние, заставляя компонент повторно рендерить снова, что установит новый тайм-аут. , который...
Итак, как вы уже выяснили, способ использовать setTimeout или setInterval с перехватчиками - это обернуть их в useEffect, например:
React.useEffect(() => {
const timeoutID = window.setTimeout(() => {
...
}, 1000);
return () => window.clearTimeout(timeoutID );
}, []);
Как и deps = [], обратный вызов useEffect будет вызываться только один раз. Затем обратный вызов, который вы вернете, будет вызван при размонтировании компонента.
В любом случае, я бы посоветовал вам создать свой собственный хук useTimeout, чтобы вы могли DRY и упростить свой код, используя setTimeoutдекларативно, как Дэн Абрамов предлагает для setInterval в Делаем setInterval декларативным с помощью React Hooks, что очень похоже:
function useTimeout(callback, delay) {
const timeoutRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setTimeout kicks in, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// timeout will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the timeout:
React.useEffect(() => {
if (typeof delay === 'number') {
timeoutRef.current = window.setTimeout(() => callbackRef.current(), delay);
// Clear timeout if the components is unmounted or the delay changes:
return () => window.clearTimeout(timeoutRef.current);
}
}, [delay]);
// In case you want to manually clear the timeout from the consuming component...:
return timeoutRef;
}
const App = () => {
const [isLoading, setLoading] = React.useState(true);
const [showLoader, setShowLoader] = React.useState(false);
// Simulate loading some data:
const fakeNetworkRequest = React.useCallback(() => {
setLoading(true);
setShowLoader(false);
// 50% of the time it will display the loder, and 50% of the time it won't:
window.setTimeout(() => setLoading(false), Math.random() * 4000);
}, []);
// Initial data load:
React.useEffect(fakeNetworkRequest, []);
// After 2 second, we want to show a loader:
useTimeout(() => setShowLoader(true), isLoading ? 2000 : null);
return (<React.Fragment>
<button onClick = { fakeNetworkRequest } disabled = { isLoading }>
{ isLoading ? 'LOADING... ?' : 'LOAD MORE ?' }
</button>
{ isLoading && showLoader ? <div className = "loader"><span className = "loaderIcon">?</span></div> : null }
{ isLoading ? null : <p>Loaded! ✨</p> }
</React.Fragment>);
}
ReactDOM.render(<App />, document.querySelector('#app'));body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
#app {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
button {
margin: 32px 0;
padding: 8px;
border: 2px solid black;
background: transparent;
cursor: pointer;
border-radius: 2px;
}
.loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 128px;
background: white;
}
.loaderIcon {
animation: spin linear infinite .25s;
}
@keyframes spin {
from { transform:rotate(0deg) }
to { transform:rotate(360deg) }
}<script src = "https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id = "app"></div>Помимо создания более простого и чистого кода, это позволяет вам автоматически очищать тайм-аут, передавая delay = null, а также возвращает идентификатор тайм-аута, если вы хотите отменить его вручную (это не рассматривается в сообщениях Дэна).
Если вы ищете аналогичный ответ для setInterval, а не для setTimeout, проверьте это: https://stackoverflow.com/a/59274004/3723993.
Вы также можете найти декларативную версию setTimeout и setInterval, useTimeout и useInterval, несколько дополнительных ловушек, написанных на TypeScript в https://www.npmjs.com/package/@swyg/corre.
@mystrdat Это ☝️ может ответить на ваш вопрос о том, как сбросить таймер при изменении некоторых свойств. В этом примере просто используйте эти реквизиты для передачи delay или null в useInterval. Если вы пройдете null, тайм-аут будет очищен для вас.
@loopmode То же самое. Это ☝️ может ответить на ваш вопрос об очистке таймера при изменении некоторых свойств.
Я написал хук реакции, чтобы больше никогда не приходилось иметь дело с тайм-аутами. работает так же, как React.useState ():
const [showLoading, setShowLoading] = useTimeoutState(false, {timeout: 5000})
// will set show loading after 5000ms
setShowLoading(true)
// overriding and timeouts after 1000ms
setShowLoading(true, { timeout: 1000})
Установка нескольких состояний обновит тайм-аут, и он истечет после того же миллисекунды, что и последний установленный setState.
Vanilla js (не тестировался, есть машинописная версия):
import React from "react"
// sets itself automatically to default state after timeout MS. good for setting timeouted states for risky requests etc.
export const useTimeoutState = (defaultState, opts) => {
const [state, _setState] = React.useState(defaultState)
const [currentTimeoutId, setCurrentTimeoutId] = React.useState()
const setState = React.useCallback(
(newState: React.SetStateAction, setStateOpts) => {
clearTimeout(currentTimeoutId) // removes old timeouts
newState !== state && _setState(newState)
if (newState === defaultState) return // if already default state, no need to set timeout to set state to default
const id = setTimeout(
() => _setState(defaultState),
setStateOpts?.timeout || opts?.timeout
)
setCurrentTimeoutId(id)
},
[currentTimeoutId, state, opts, defaultState]
)
return [state, setState]
}
Машинопись:
import React from "react"
interface IUseTimeoutStateOptions {
timeout?: number
}
// sets itself automatically to default state after timeout MS. good for setting timeouted states for risky requests etc.
export const useTimeoutState = <T>(defaultState: T, opts?: IUseTimeoutStateOptions) => {
const [state, _setState] = React.useState<T>(defaultState)
const [currentTimeoutId, setCurrentTimeoutId] = React.useState<number | undefined>()
// todo: change any to React.setStateAction with T
const setState = React.useCallback(
(newState: React.SetStateAction<any>, setStateOpts?: { timeout?: number }) => {
clearTimeout(currentTimeoutId) // removes old timeouts
newState !== state && _setState(newState)
if (newState === defaultState) return // if already default state, no need to set timeout to set state to default
const id = setTimeout(
() => _setState(defaultState),
setStateOpts?.timeout || opts?.timeout
) as number
setCurrentTimeoutId(id)
},
[currentTimeoutId, state, opts, defaultState]
)
return [state, setState] as [
T,
(newState: React.SetStateAction<T>, setStateOpts?: { timeout?: number }) => void
]
}```
const[seconds, setSeconds] = useState(300);
function TimeOut() {
useEffect(() => {
let interval = setInterval(() => {
setSeconds(seconds => seconds -1);
}, 1000);
return() => clearInterval(interval);
}, [])
function reset() {
setSeconds(300);
}
return (
<div>
Count Down: {seconds} left
<button className = "button" onClick = {reset}>
Reset
</button>
</div>
)
}
Обязательно импортируйте useState и useEffect. Также добавьте логику для остановки таймера на 0.
Вы думали об остановке интервала при достижении 0?
Если вы хотите создать такую кнопку, как «start», то использование хука «useInterval» может не подойти, поскольку response не позволяет вызывать хуки, кроме как в верхней части компонента.
export default function Loading() {
// if data fetching is slow, after 1 sec i will show some loading animation
const [showLoading, setShowLoading] = useState(true)
const interval = useRef();
useEffect(() => {
interval.current = () => setShowLoading(true);
}, [showLoading]);
// make a function like "Start"
// const start = setInterval(interval.current(), 1000)
setInterval(() => interval.current(), 1000);
console.info('this message will render every second')
return 1
}
Если ваш таймаут находится в "конструкции if" попробуйте это:
useEffect(() => {
let timeout;
if (yourCondition) {
timeout = setTimeout(() => {
// your code
}, 1000);
} else {
// your code
}
return () => {
clearTimeout(timeout);
};
}, [yourDeps]);
export const useTimeout = () => {
const timeout = useRef();
useEffect(
() => () => {
if (timeout.current) {
clearTimeout(timeout.current);
timeout.current = null;
}
},
[],
);
return timeout;
};
Вы можете использовать простой перехватчик, чтобы поделиться логикой тайм-аута.
const timeout = useTimeout();
timeout.current = setTimeout(your conditions)
Используйте setTimeout в ваших компонентах React, чтобы выполнить функцию или блок кода через определенный период времени. Давайте посмотрим, как использовать setTimeout в React. Также существует аналогичный метод под названием setInterval
useEffect(() => {
const timer = setTimeout(() => {
console.info('This will run after 1 second!')
}, 1000);
return () => clearTimeout(timer);
}, []);
В случае интервалов, чтобы избежать постоянного присоединения (монтирования) и отсоединения (размонтирования) метода setInterval к циклу событий с использованием перехватчика useEffect в примерах, приведенных другими, вы можете вместо этого использовать useReducer.
Представьте себе сценарий, в котором с учетом seconds и minutes вы должны отсчитывать время ...
Ниже мы получили функцию reducer, которая выполняет обратный отсчет.
const reducer = (state, action) => {
switch (action.type) {
case "cycle":
if (state.seconds > 0) {
return { ...state, seconds: state.seconds - 1 };
}
if (state.minutes > 0) {
return { ...state, minutes: state.minutes - 1, seconds: 60 };
}
case "newState":
return action.payload;
default:
throw new Error();
}
}
Теперь все, что нам нужно сделать, это отправить действие cycle в каждый интервал:
const [time, dispatch] = useReducer(reducer, { minutes: 0, seconds: 0 });
const { minutes, seconds } = time;
const interval = useRef(null);
//Notice the [] provided, we are setting the interval only once (during mount) here.
useEffect(() => {
interval.current = setInterval(() => {
dispatch({ type: "cycle" });
}, 1000);
// Just in case, clear interval on component un-mount, to be safe.
return () => clearInterval(interval.current);
}, []);
//Now as soon as the time in given two states is zero, remove the interval.
useEffect(() => {
if (!minutes && !seconds) {
clearInterval(interval.current);
}
}, [minutes, seconds]);
// We could have avoided the above state check too, providing the `clearInterval()`
// inside our reducer function, but that would delay it until the next interval.
Можете поделиться кодом useState и setShowLoading