진행하는 프로젝트에서 스톱워치를 구현해야할 일이 있어서 스톱워치를 만들어보았다.
<Timer />
) <Container gap={12}>
<Timer />
<StartStopButton>
{running ? 'Stop' : 'Start'}
</StartStopButton>
<ResetButton>Reset</ResetButton>
<Records />
<LapButton>Lap</LapButton>
</Container>
// 1. 스톱 워치가 작동(running) 하고 있는지 여부
const [running, setRunning] = useState<boolean>(false);
// 2. 실시간으로 측정되고 있는 시간
const [time, setTime] = useState<number>(0);
// 3. lap 버튼을 눌렀을 때에 기록된 시간들
const [laps, setLaps] = useState<Record[]>([]);
// timer의 실시간(?)을 관리하는 변수
const intervalRef = useRef<NodeJS.Timeout | undefined>(undefined);
필요한 함수는 총 5가지가 있다.
- 스톱워치를 시작하고, 정지하는 함수
- 스톱워치를 reset 하는 함수
- 해당 시간을 Lap에 기록하는 함수
- 특정 lap을 삭제하는 기능
- 기록된 시간을 0:00:00 형태로 formating 하는 함수
하나하나 차근차근 구현을 해보자.
START
STOP
const startStopwatch = () => {
if (!running) {
intervalRef.current = setInterval(() => {
setTime((prevTime) => prevTime + 1000);
}, 1000);
setRunning(true);
} else {
clearInterval(intervalRef.current);
setRunning(false);
}
};
reset은 지금까지 변경된 state와 ref를 초기화 시켜 주면 된다.
const resetStopwatch = () => {
clearInterval(intervalRef.current);
setTime(0);
setLaps([]);
setRunning(false);
};
나는 삭제 및 DB에 저장 등의 기능을 고려하여, record에 id를 주어 { id : number , lap : number }
의 객체 타입으로 정의하였다.
const recordLap = () => {
const newLap = {
id: laps.length === 0 ? 1 : laps[laps.length - 1].id + 1,
lap: time,
};
// patchRecord(newLap); : DB에 해당 기록을 patch하는 로직
setLaps((prevLaps) => [...prevLaps, newLap]);
};
patchRecord 부분은 DB에 해당 기록을 patch하는 로직으로 생략가능!
삭제하는 기능은 해당 랩을 누르고 삭제버튼을 눌렀을때 id를 받아 해당 id의 기록을 삭제하는 기능이다.
const deleteLap = (id: number) => {
const filteredLaps = laps
.filter((record) => record.id !== id)
.map((record, index) => ({ ...record, id: index + 1 }));
// updateRecords({ laps: filteredLaps }); DB에 기록들을 PATCH하는 로직
setLaps(filteredLaps);
};
uploadRecords 부분은 DB에 기록하는 로직이라 생략 가능!
이제 마지막으로 기록을 0:00:00 의 형태로 변환하는 로직이다. time은 지금까지 ms (=> number) 로 업데이트 되고 있었다. 사용자에게 보여주기 위해서는 시/분/초의 계산이 필요하다.
export const formatTime = (timeInMillis: number): string => {
const hours = Math.floor(timeInMillis / 3600000);
const minutes = Math.floor((timeInMillis % 3600000) / 60000);
const seconds = Math.floor((timeInMillis % 60000) / 1000);
const formattedTime = `${hours}:${minutes < 10 ? '0' : ''}${minutes}:${
seconds < 10 ? '0' : ''
}${seconds}`;
return formattedTime;
};
const LapTime = ({}: P) => {
const [running, setRunning] = useState<boolean>(false);
const [time, setTime] = useState<number>(0);
const [laps, setLaps] = useState<Record[]>([]);
const intervalRef = useRef<NodeJS.Timeout | undefined>(undefined);
const startStopwatch = () => {
if (!running) {
intervalRef.current = setInterval(() => {
setTime((prevTime) => prevTime + 10);
}, 10);
setRunning(true);
} else {
clearInterval(intervalRef.current);
setRunning(false);
}
};
const resetStopwatch = () => {
clearInterval(intervalRef.current);
setTime(0);
setLaps([]);
setRunning(false);
};
const recordLap = () => {
const newLap = {
id: laps.length === 0 ? 1 : laps[laps.length - 1].id + 1,
lap: time,
};
postRecord(newLap);
setLaps((prevLaps) => [...prevLaps, newLap]);
};
return (
<Container gap={12}>
<Timer lapTime={formatTime(time)} />
<ButtonWrapper>
<StartButton onClick={startStopwatch}>
{running ? 'Stop' : 'Start'}
</StartButton>
<ResetButton onClick={resetStopwatch}>Reset</ResetButton>
</ButtonWrapper>
<Records laps={laps} setLaps={setLaps} />
{running && <LapButton onClick={recordLap}>Lap</LapButton>}
</Container>
);
};
export default LapTime;
timer에는 format 된 time을 넘겨주고,
startStopButton은 동작하지 않을땐 start, 동작중일땐 stop을 보여준다.
records에는 laps를 넘겨주어 map하여 기록들을 보여주면 될 것이다!
lap 버튼은 동작하고 있지 않을때에는 시간이 기록될 필요가 없으므로, running 중일때만 보여주면 된다.
정보에 감사드립니다.