#reactjs #react-hooks
#reactjs #реагирующие крючки
Вопрос:
Я пытаюсь реализовать таймер обратного отсчета самостоятельно, просто чтобы узнать больше о крючках. Я знаю, что есть библиотеки, но не хочу их использовать. проблема с моим кодом в том, что я не могу получить обновленное состояние внутри функции «таймер», которая обновляется в функции таймера запуска. Я пытаюсь реализовать таймер, который будет иметь триггеры для запуска, остановки и возобновления и может быть запущен вручную. другим компонентом, использующим компонент обратного отсчета
import React, { useState } from 'react';
const Countdown = ({ countDownTimerOpt }) => {
const [getObj, setObj] = useState({
formatTimer: null,
countDownTimer: 0,
intervalObj: null,
});
const { formatTimer, countDownTimer, intervalObj } = getObj;
if (countDownTimerOpt > 0 amp;amp; intervalObj === null) {
startTimer();
}
function startTimer() {
const x = setInterval(() => {
timer();
}, 1000);
setObj((prev) => ({
...prev,
countDownTimer: countDownTimerOpt,
intervalObj: x,
}));
}
function timer() {
var days = Math.floor(countDownTimer / 24 / 60 / 60);
var hoursLeft = Math.floor(countDownTimer - days * 86400);
var hours = Math.floor(hoursLeft / 3600);
var minutesLeft = Math.floor(hoursLeft - hours * 3600);
var minutes = Math.floor(minutesLeft / 60);
var remainingSeconds = countDownTimer % 60;
const formatTimer1 =
pad(days)
':'
pad(hours)
':'
pad(minutes)
':'
pad(remainingSeconds);
if (countDownTimer === 0) {
clearInterval(intervalObj);
} else {
setObj((prev) => ({
...prev,
formatTimer: formatTimer1,
countDownTimer: prev['countDownTimer'] - 1,
}));
}
}
function pad(n) {
return n < 10 ? '0' n : n;
}
return <div>{formatTimer ? formatTimer : Math.random()}</div>;
};
export default Countdown;
import React, { useState, useEffect } from 'react';
import Timer from '../../components/countdown-timer/countdown.component';
const Training = () => {
const [getValue, setValue] = useState(0);
useEffect(() => {
const x = setTimeout(() => {
console.log('setTimeout');
setValue(10000);
}, 5000);
return () => clearInterval(x);
}, []);
return <Timer countDownTimerOpt={getValue} />;
не хотите использовать какой-либо установленный интервал внутри страницы обучения, поскольку компонент обратного отсчета также будет использоваться на странице экзамена
Ответ №1:
Обычно с помощью хуков я бы объединил вашу функциональность в пользовательский хук и использовал его в разных местах.
const useTimer = (startTime) => {
const [time, setTime] = useState(startTime)
const [intervalID, setIntervalID] = useState(null)
const hasTimerEnded = time <= 0
const isTimerRunning = intervalID != null
const update = () => {
setTime(time => time - 1)
}
const startTimer = () => {
if (!hasTimerEnded amp;amp; !isTimerRunning) {
setIntervalID(setInterval(update, 1000))
}
}
const stopTimer = () => {
clearInterval(intervalID)
setIntervalID(null)
}
// clear interval when the timer ends
useEffect(() => {
if (hasTimerEnded) {
clearInterval(intervalID)
setIntervalID(null)
}
}, [hasTimerEnded])
// clear interval when component unmounts
useEffect(() => () => {
clearInterval(intervalID)
}, [])
return {
time,
startTimer,
stopTimer,
}
}
Вы, конечно, можете добавить функцию сброса или внести другие изменения, но использование может выглядеть так:
const Training = () => {
const { time, startTimer, stopTimer } = useTimer(20)
return <>
<div>{time}</div>
<button onClick={startTimer}>start</button>
<button onClick={stopTimer}>stop</button>
</>
}
Комментарии:
1. можете ли вы объяснить, почему я получал CountDownTimer = 0 внутри функции timer (), даже если состояние менялось
2. Я думаю, это из-за концепции, называемой замыканиями .
setInterval
вызывается при первом рендеринге иtimer
создается внутри компонента. Поэтомуtimer
имеет доступcountDownTimer
в первую очередь. При следующем рендеринге состояние изменится, и будет создана новаяtimer
функция с доступом к измененному состоянию, но интервал все равно будет вызывать старуюtimer
функцию.3. Хорошо, спасибо, я обновил ваш код в соответствии с моими требованиями внутри ваших пользовательских крючков у меня есть return setTime, затем внутри обучающего компонента удален startTimer onclick добавлен useEffect в обучающем компоненте, который заставил сеть вызывать используемое время установки, а не вызывать startTimer() здесь время установки работает нормально, но не startTimer()
Ответ №2:
Вы можете создать useCountDown
крючок следующим образом (в Typescript) :
import { useEffect, useRef, useState } from 'react';
export const useCountDown: (
total: number,
ms?: number,
) => [number, () => void, () => void, () => void] = (
total: number,
ms: number = 1000,
) => {
const [counter, setCountDown] = useState(total);
const [startCountDown, setStartCountDown] = useState(false);
// Store the created interval
const intervalId = useRef<number>();
const start: () => void = () => setStartCountDown(true);
const pause: () => void = () => setStartCountDown(false);
const reset: () => void = () => {
clearInterval(intervalId.current);
setStartCountDown(false);
setCountDown(total);
};
useEffect(() => {
intervalId.current = setInterval(() => {
startCountDown amp;amp; counter > 0 amp;amp; setCountDown(counter => counter - 1);
}, ms);
// Clear interval when count to zero
if (counter === 0) clearInterval(intervalId.current);
// Clear interval when unmount
return () => clearInterval(intervalId.current);
}, [startCountDown, counter, ms]);
return [counter, start, pause, reset];
};
Демонстрация использования: https://codesandbox.io/s/usecountdown-hook-56lqv