Я пробую новый Реагировать на хуки и имею компонент Clock со счетчиком, который должен увеличиваться каждую секунду. Однако значение не превышает единицы.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, 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>


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


Причина в том, что обратный вызов, переданный в закрытие setInterval, обращается только к переменной time в первом рендере, у него нет доступа к новому значению time в последующем рендеринге, потому что useEffect() не вызывается во второй раз.
time всегда имеет значение 0 в обратном вызове setInterval.
Как и в случае с setState, с которым вы знакомы, перехватчики состояния имеют две формы: одну, в которой он принимает обновленное состояние, и форму обратного вызова, в которую передается текущее состояние. Вы должны использовать вторую форму и считывать последнее значение состояния в setState. обратный вызов, чтобы убедиться, что у вас есть последнее значение состояния, прежде чем увеличивать его.
Bonus: Alternative Approaches
Dan Abramov, goes in-depth into the topic about using
setIntervalwith hooks in his blog post and provides alternative ways around this issue. Highly recommend reading it!
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(prevTime => prevTime + 1); // <-- Change this line!
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, 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>@YangshunTay Если я просто хочу прочитать значение состояния в setInterval, как мне это сделать?
@neosarchizo Вы читали пост Дэна? overrected.io/making-setinterval-declarative-with-react-hoo ks. Если вы просто хотите прочитать его, вы можете прочитать обновленное значение как часть рендеринга внизу. Если вы хотите вызвать побочные эффекты, вы можете добавить перехватчик useEffect() и добавить это состояние в массив зависимостей.
Как бы это выглядело, если бы вы хотели периодически выводить текущее состояние с помощью console.info в функции setInterval?
Я хочу прочитать время (в setInterval) и обновить, если оно больше, чем какое-то время. Как этого добиться?
@neosarchizo "Если вы просто хотите прочитать это, вы можете прочитать обновленное значение как часть рендеринга внизу". Не понял, не могли бы вы немного уточнить
Это сообщение в блоге отличное. Однако мне бы хотелось, чтобы он был переведен на более общий пример. Объясняя, как это сделать для любого обратного вызова, было бы неплохо перепроверить мою работу.
Потратил примерно 2 часа, чтобы найти это решение и понимание ....
Функция useEffect оценивается только один раз при монтировании компонента, если предоставляется пустой список ввода.
Альтернативой setInterval является установка нового интервала с setTimeout каждый раз при обновлении состояния:
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => {
setTime(time + 1);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [time]);
Влияние setTimeout на производительность незначительно, и его можно игнорировать. Если компонент не чувствителен ко времени до точки, где вновь установленные тайм-ауты вызывают нежелательные эффекты, приемлемы подходы как setInterval, так и setTimeout.
Альтернативным решением было бы использовать useReducer, так как ему всегда будет передаваться текущее состояние.
function Clock() {
const [time, dispatch] = React.useReducer((state = 0, action) => {
if (action.type === 'add') return state + 1
return state
});
React.useEffect(() => {
const timer = window.setInterval(() => {
dispatch({ type: 'add' });
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, 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>Почему здесь useEffect вызывается несколько раз для обновления времени, в то время как массив зависимостей пуст, а это означает, что useEffect следует вызывать только при первом рендеринге компонента / приложения?
@BlackMath Функция внутри useEffect вызывается только один раз, когда компонент действительно сначала отрисовывается. Но внутри него находится setInterval, который регулярно меняет время. Предлагаю вам немного почитать о setInterval, после этого все станет яснее! developer.mozilla.org/en-US/docs/Web/API/…
Сообщите React о повторном рендеринге при изменении времени.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, [time]);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, 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>Проблема в том, что таймер будет очищаться и сбрасываться после каждого изменения count.
И поскольку setTimeout() предпочтительнее, как указывает Эстус
Эти решения не работают для меня, потому что мне нужно получить переменную и сделать что-то, а не просто обновить ее.
У меня есть обходной путь, чтобы получить обновленное значение хука с обещанием
Например:
async function getCurrentHookValue(setHookFunction) {
return new Promise((resolve) => {
setHookFunction(prev => {
resolve(prev)
return prev;
})
})
}
С этим я могу получить значение внутри функции setInterval следующим образом
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
Это плохая практика, установщик состояния React должен быть чистым, без побочных эффектов. Кроме того, вызов некоторого сеттера только для получения текущего значения все равно вызовет повторный рендеринг текущего компонента.
Как указывали другие, проблема в том, что useState вызывается только один раз (как deps = []) для установки интервала:
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => window.clearInterval(timer);
}, []);
Затем каждый раз, когда тикает setInterval, он фактически вызывает setTime(time + 1), но time всегда будет хранить значение, которое оно имело изначально, когда был определен обратный вызов (закрытие) setInterval.
Вы можете использовать альтернативную форму установщика useState и предоставить обратный вызов, а не фактическое значение, которое вы хотите установить (как и в случае с setState):
setTime(prevTime => prevTime + 1);
Но я бы посоветовал вам создать свой собственный хук useInterval, чтобы вы могли DRY и упростить свой код, используя setIntervalдекларативно, как предлагает Дэн Абрамов здесь, в Делаем setInterval декларативным с помощью React Hooks:
function useInterval(callback, delay) {
const intervalRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setInterval ticks again, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// interval will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the interval:
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
// Clear interval if the components is unmounted or the delay changes:
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
// Returns a ref to the interval ID in case you want to clear it manually:
return intervalRef;
}
const Clock = () => {
const [time, setTime] = React.useState(0);
const [isPaused, setPaused] = React.useState(false);
const intervalRef = useInterval(() => {
if (time < 10) {
setTime(time + 1);
} else {
window.clearInterval(intervalRef.current);
}
}, isPaused ? null : 1000);
return (<React.Fragment>
<button onClick = { () => setPaused(prevIsPaused => !prevIsPaused) } disabled = { time === 10 }>
{ isPaused ? 'RESUME ⏳' : 'PAUSE ?' }
</button>
<p>{ time.toString().padStart(2, '0') }/10 sec.</p>
<p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p>
</React.Fragment>);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
p + p {
margin-top: 8px;
}
#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;
}<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, а также возвращает идентификатор интервала, если вы хотите отменить его вручную (это не рассматривается в сообщениях Дэна).
На самом деле, это также можно улучшить, чтобы он не перезапускал delay при снятии паузы, но я думаю, для большинства случаев использования этого достаточно.
Если вы ищете аналогичный ответ для setTimeout, а не для setInterval, проверьте это: https://stackoverflow.com/a/59274757/3723993.
Вы также можете найти декларативную версию setTimeout и setInterval, useTimeout и useInterval, а также специальный перехватчик useThrottledCallback, написанный на TypeScript в https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a.
Сделайте, как показано ниже, он отлично работает.
const [count , setCount] = useState(0);
async function increment(count,value) {
await setCount(count => count + 1);
}
//call increment function
increment(count);
Где в вашем ответе используется setInterval?
Параметры increment здесь тоже бесполезны.
useRef может решить эту проблему, вот аналогичный компонент, который увеличивает счетчик каждые 1000 мс.
import { useState, useEffect, useRef } from "react";
export default function App() {
const initalState = 0;
const [count, setCount] = useState(initalState);
const counterRef = useRef(initalState);
useEffect(() => {
counterRef.current = count;
})
useEffect(() => {
setInterval(() => {
setCount(counterRef.current + 1);
}, 1000);
}, []);
return (
<div className = "App">
<h1>The current count is:</h1>
<h2>{count}</h2>
</div>
);
}
и я думаю, что эта статья поможет вам использовать интервал для реагирующих хуков.
Я скопировал код из этого блога. Все кредиты владельцу. https://overrected.io/making-setinterval-declarative-with-react-hooks/
Единственное, что я адаптировал этот код React к коду React Native, поэтому, если вы являетесь нативным кодировщиком, просто скопируйте его и адаптируйте к тому, что вы хотите. Адаптировать очень легко!
import React, {useState, useEffect, useRef} from "react";
import {Text} from 'react-native';
function Counter() {
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest function.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
const [count, setCount] = useState(0);
useInterval(() => {
// Your custom logic here
setCount(count + 1);
}, 1000);
return <Text>{count}</Text>;
}
export default Counter;
Есть прекрасные объяснения, почему это происходит. В случае, если кто-то хочет также получить значение stackoverflow.com/a/57679222/4427870, это сильно недооцененный прием.