리액트 타이머 만들기

hhkim·2021년 7월 23일
9
post-thumbnail

🔗 react-timer 완성본

현재 시각을 보여주는 시계와 타이머 구현하기 (feat. 라우터)


프로젝트 세팅

  • create-react-app 프로젝트 생성
    npx create-react-app react-timer
  • react-router-dom 설치
    npm install react-router-dom

시계 구현

  • setInterval()로 1초마다 현재 시간 정보를 갖고 있는 Date 객체를 생성하고 getHours(), getMinutes(), getSeconds()로 시, 분, 초를 받아온다.
  • 그냥 setInterval()을 사용하면 렌더링이 될 때마다 setInterval()이 계속 새로 생성되어 성능의 저하가 발생한다.
  • 임의의 변수를 만들어서 setInterval()을 저장해두고 useEffect()가 이 변수를 참조하도록 하면..? 역시나 렌더링 될 때마다 새로운 setInterval()을 만드는 건 마찬가지
    👉 useEffect()를 사용해서 처음 마운트되었을 때 한 번만 setInterval()을 생성하도록 한다. (useEffect() 공식 문서)

    useEffect()의 두 번째 인자로 빈 배열([])을 전달하면 처음 마운트 되었을 때 한 번만 실행된다.
    useEffect()는 컴포넌트가 언마운트될 때 실행될 정리(clean-up) 함수를 반환할 수 있으므로, clearInterval()을 반환하여 setInterval()이 좀비화되는 걸 방지하도록 하자.

  • 각 값은 number 타입이므로 string으로 만든 후에 padStart()로 2자리를 맞춰준다.
import React, { useState, useRef, useEffect } from 'react';

const padNumber = (num, length) => {
  return String(num).padStart(length, '0');
};

const Clock = () => {
  let now = new Date();
  const [hour, setHour] = useState(padNumber(now.getHours(), 2));
  const [min, setMin] = useState(padNumber(now.getMinutes(), 2));
  const [sec, setSec] = useState(padNumber(now.getSeconds(), 2));
  const interval = useRef(null);

  useEffect(() => {
    interval.current = setInterval(() => {
      now = new Date();
      setHour(padNumber(now.getHours(), 2));
      setMin(padNumber(now.getMinutes(), 2));
      setSec(padNumber(now.getSeconds(), 2));
    }, 1000);
    // clean-up 함수 리턴!
    return () => clearInterval(interval.current);
  }, []);

  return (
    <div>
      {hour} : {min} : {sec}
    </div>
  );
};

export default Clock;

타이머 구현

  • 입력 받은 시, 분, 초를 더해 초단위로 변환한 initialTime 변수 선언
    👉 이걸 1초마다 1씩 줄이면서 시, 분, 초 state를 재설정하고 setInterval()을 멈추는 조건으로 사용할 예정
  • useEffect() 안에서 변경한 변수는 유지가 안 된대요.. 🤡
    👉 useRef()를 사용하자! (useRef() 공식 문서)
import React, { useState, useEffect, useRef } from 'react';

const padNumber = (num, length) => {
  return String(num).padStart(length, '0');
};

const Timer = (props) => {
  // 아무것도 입력하지 않으면 undefined가 들어오기 때문에 유효성 검사부터..
  const tempHour = props.hour ? parseInt(props.hour) : 0;
  const tempMin = props.min ? parseInt(props.min) : 0;
  const tempSec = props.sec ? parseInt(props.sec) : 0;
  // 타이머를 초단위로 변환한 initialTime과 setInterval을 저장할 interval ref
  const initialTime = useRef(tempHour * 60 * 60 + tempMin * 60 + tempSec);
  const interval = useRef(null);

  const [hour, setHour] = useState(padNumber(tempHour, 2));
  const [min, setMin] = useState(padNumber(tempMin, 2));
  const [sec, setSec] = useState(padNumber(tempSec, 2));

  useEffect(() => {
    interval.current = setInterval(() => {
      initialTime.current -= 1;
      setSec(padNumber(initialTime.current % 60, 2));
      setMin(padNumber(parseInt(initialTime.current / 60), 2));
      setHour(padNumber(parseInt(initialTime.current / 60 / 60), 2));
    }, 1000);
    return () => clearInterval(interval.current);
  }, []);

  // 초가 변할 때만 실행되는 useEffect
  // initialTime을 검사해서 0이 되면 interval을 멈춘다.
  useEffect(() => {
    if (initialTime.current <= 0) {
      clearInterval(interval.current);
    }
  }, [sec]);

  return (
    <div>
      {hour} : {min} : {sec}
    </div>
  );
};

export default Timer;

깃헙 페이지에 배포

  • gh-pages 설치
    npm i gh-pages

package.json

  • homepage 추가
    "homepage": "https://[사용자명].github.io/[프로젝트명]/"
  • scripts에 deploy, predeploy 추가
    프로젝트를 build하면 build 폴더가 생성됨
 "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "deploy": "gh-pages -d build",
    "predeploy": "npm run build"	// deploy 명령을 하면 자동으로 실행됨
},
  • npm run deploy로 배포

❗ BrowserRouter를 썼을 때 페이지가 gh-pages에 보이지 않는 문제

  • 이번 프로젝트에서 HashRouter가 아닌 BrowserRouter를 써서 루트(/) 경로가 제대로 인식되지 않는 문제가 발생했다.
  • 여기를 참고해서 BrowserRouter에 프로젝트의 기본 URL을 설정하는 basename props 추가
<BrowserRouter basename={process.env.PUBLIC_URL}>
    <Route path="/" exact={true} component={Input} />
    <Route path="/time" component={Time} />
</BrowserRouter>

참고

번역 / 리액트 훅스 컴포넌트에서 setInterval 사용 시의 문제점
React 컴포넌트에서 타이머 설정하기 (with Hooks)

2개의 댓글

comment-user-thumbnail
2021년 11월 18일

글 잘 보았습니다. 그런데, 타이머 구현 에서 '시' 와 '초' 는 잘 나오는데 '분' 영역은 3자리수의 숫자로 나오는것 같네요 분 영역 구하는 로직이 무언가 잘못된걸까요??

1개의 답글