Вопрос проверяет умение работать с таймерами в React, корректно управлять жизненным циклом и избегать типичных багов с интервалами.
Проще всего хранить “прошедшие секунды” в useState, а запуск обновления сделать в useEffect через setInterval. Внутри интервала увеличивать значение каждую секунду через функциональное обновление setState(prev => prev + 1). Обязательно вернуть функцию очистки, чтобы остановить интервал при размонтировании. Для более точного секундомера лучше хранить startTime в useRef и пересчитывать прошедшее время от реального времени, чтобы уменьшить “дрейф”.
Секундомер можно сделать “простым” (приблизительным) и “более точным” (через реальное время).
Идея простая: раз в 1000 мс увеличиваем счётчик.
import { useEffect, useState } from "react";
export function Stopwatch() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = window.setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);
return () => window.clearInterval(id);
}, []);
return <div>{seconds}s</div>;
}
Почему используется setSeconds((s) => s + 1)
это защищает от “устаревшего значения” в замыкании и работает стабильно.
Интервалы в браузере могут “плыть” (вкладка неактивна, нагрузка, таймеры троттлятся). Поэтому можно хранить время старта и вычислять прошедшее время от Date.now().
import { useEffect, useRef, useState } from "react";
export function StopwatchAccurate() {
const startRef = useRef<number>(Date.now());
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = window.setInterval(() => {
const diffMs = Date.now() - startRef.current;
setSeconds(Math.floor(diffMs / 1000));
}, 1000);
return () => window.clearInterval(id);
}, []);
return <div>{seconds}s</div>;
}
Что даёт useRef
значение startRef.current сохраняется между рендерами и не вызывает ререндер само по себе.
Для простых UI-задач достаточно инкремента раз в секунду, но для “настоящего” секундомера лучше считать секунды от реального времени, чтобы таймер меньше “уезжал”.