ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TIL] Timer 만들기
    IT 지식 2020. 12. 6. 15:49
    728x90

    이번 프로젝트를 진행하면서 제가 직접 타이머를 만들어야 했습니다. 라이브러리를 쓰지 않고 직접 만들어보려고 하니, 너무 오래 걸리고 쉽지 않았습니다. 지금 현재도 완벽하게는 작동하지 않지만, 그래도 그럭저럭 움직이는 타이머를 만들어보고 나니 저처럼 직접 타이머를 만들어야 하는 분들이 있을 것 같아, 제가 타이머를 만들던 과정을 남겨놓고 코드를 남겨 놓으면 다른 분들에게 도움이 될 것 같아 이번 포스팅을 작성하게 되었습니다.

     

    해당 타이머는 React를 활용해서 만들었습니다. 

     

    1. 초기 타이머 

     

    맨 처음 만든 타이머는 setTimeOut 함수를 통해 만들었습니다. Date 객체를 활용하여 현재 시각이 매 초 찍히게 만들었고, 시작시간을 state로 기록하여 현재 시각에서 시작 시간을 빼주는 방식으로 로직을 생각했습니다. 하지만 이내 곧 문제에 봉착하게 되었습니다.

     

    문제

     

    1. setTimeOut 함수가 자동 실행
    2. 시간이 제대로 계산 되지 않음
    3. 카운트 종료가 안됨

    이런 문제에 봉착하게 되었고 주변 분들과 인터넷에 조언을 구해본 결과 useEffect를 사용하면 좋다는 것을 알게 되었습니다. 

     

     

    2. 1차 해결책 useEffect

     

    useEffect를 사용해서 타이머를 업그레이드 하고 났는데 이번엔 다른 문제에 봉착하게 되었습니다.

     

     

    새로운 문제 

     

    1. setInterval가 두번 호출됨
    2. 이 놈이 부르지도 않았는데 호출됨
    3. new Date 객체값을 가지고 오지 못하고 있음

     

    당시 코드

     const [onWorking, setOnWorking] = useState(false);
     const [startTime, setStartTime] = useState(null);
     const [nowTime, setNowTime] = useState(null);
     const [workingHours, setWorkingHours] = useState();
      
      
      let interval;
    
      const timer = () => {
        if (!startTime) {
          setStartTime(new Date());
          setOnWorking(!onWorking);
        }
        setNowTime(new Date());
        setWorkingHours(new Date(nowTime - startTime));
    
      };
    
      useEffect(() => {
        if (onWorking) {
          interval = setInterval(() => timer(), 1000);
        }
        return () => clearInterval(interval);
      }, [workingHours]);
    
      const finishTimer = () => {
        setOnWorking(!onWorking);
        setStartTime(null);
        setWorkingHours(null);
        clearTimeout(interval);
      };

     

     

    3. 2차 문제

     

    어찌어찌 문제를 해결하고 진행해보았지만 결국  Date 값을 코드에서 인식하지 못해 시계의 상태값이 변하지 못하고 있었습니다. 

     

     

    그 다음 문제 

     

    1. 객체값 가져오기

     

    당시 코드

     const [onWorking, setOnWorking] = useState(false);
      const [startTime, setStartTime] = useState(null);
      const [nowTime, setNowTime] = useState(null);
      const [workingHours, setWorkingHours] = useState(new Date(0));
      
      let interval;
    
      const timer = () => {
        if (!startTime) {
          setStartTime(new Date());
          setOnWorking(true);
        }
        setNowTime(new Date());
        setWorkingHours(new Date(nowTime - startTime));
    
      };
    
      useEffect(() => {
        if (onWorking) {
          interval = setInterval(() => timer(), 1000);
        }
        return () => clearInterval(interval);
      }, [workingHours, onWorking]);
    
      const resumeTimer = (e) => {
        if (workingHours.getTime() > 0) {
          setUserState(e.target.value);
          setOnWorking(true);
        }
      };
    
      const stopTimer = (e) => {
        if (workingHours.getTime() > 0) {
          setUserState(e.target.value);
          setOnWorking(false);
          clearTimeout(interval);
          setStartTime(new Date());
          setWorkingHours(new Date(nowTime - startTime));
        }
      };
    
      const finishTimer = () => {
        setOnWorking(false);
        setStartTime(null);
        setWorkingHours(new Date(0));
        clearTimeout(interval);
      };
      
       <div>
              {workingHours.getHours() - 9}시간 {workingHours.getMinutes()}분{" "}
              {workingHours.getSeconds()}초
            </div>

     

     

     

    4. 3차 문제

     

    시계가 직접 보여지는 태그 부분에서 데이터를 가공하는 방식으로 객체 값을 가져오고 보여지는 것에 성공했습니다. 하지만 역시나 새로운 문제에 봉착했습니다.

     

    새로운 문제

     

    1. 일시정지 후 새로 시작하면 시간이 틀림

     

     

    마지막으로 이 문제를 해결하기 위해 사용한 방식은 쿠키를 사용하는 것이었습니다. 쿠키를 사용해서 상태값을 react 내부의 state로 관리하는 것이 아니라, 로컬에서 관리를 했고 react 사이클 밖으로 빼냄으로써 동기적인 문제를 좀 줄일 수 있었습니다. 그렇게 완성된 최종 코드입니다.

     

    최종 코드

      const [onWorking, setOnWorking] = useState(false);
      const [workingHours, setWorkingHours] = useState(new Date(0));
    
      const [
        startTimeCookies,
        setStartTimeCookies,
        removeStartTimeCookies
      ] = useCookies(["start_time"]);
      const [
        pauseTimeCookies,
        setPauseTimeCookies,
        removePauseTimeCookies
      ] = useCookies(["pause_time"]);
      const [
        reStartTimeCookies,
        setReStartTimeCookies,
        removeReStartTimeCookies
      ] = useCookies(["restart_time"]);
      const [reStartToken, setReStartToken, removeReStartToken] = useCookies([
        "restart_token"
      ]);
      const [finishTimeCookies, setFinishTimeCookies] = useCookies(["finish_time"]);
    
      const showingButton = () => {
        setHiddenState(!hiddenState);
      };
    
      const interval = useRef();
    
      const timer = () => {
        if (!startTimeCookies.start_time) {
          setStartTimeCookies("start_time", Date.now());
          setOnWorking(true);
        } else if (startTimeCookies.start_time && reStartToken.restart_token) {
          let preCal =
            reStartTimeCookies.restart_time - pauseTimeCookies.pause_time;
          setStartTimeCookies(
            "start_time",
            Number(new Date(Number(startTimeCookies.start_time) + preCal))
          );
          removeReStartToken("restart_token");
        }
        setWorkingHours(new Date(Date.now() - startTimeCookies.start_time));
      };
    
      useEffect(() => {
        if (onWorking) {
          interval.current = setInterval(() => timer(), 1000);
        }
        return () => clearInterval(interval.current);
      }, [
        workingHours,
        onWorking,
        startTimeCookies.start_time,
        pauseTimeCookies.pause_time
      ]);
    
      const resumeTimer = (e) => {
        if (workingHours.getTime() > 0) {
          setUserState(e.target.value);
          setOnWorking(true);
          setReStartTimeCookies("restart_time", Date.now());
          setReStartToken("restart_token", "onWorking");
        }
      };
    
      const stopTimer = (e) => {
        if (workingHours.getTime() > 0) {
          setUserState(e.target.value);
          setOnWorking(false);
          setPauseTimeCookies("pause_time", Date.now());
          removeReStartToken("restart_token");
          clearTimeout(interval.current);
        }
      };
    
      const finishTimer = () => {
        setOnWorking(false);
        setWorkingHours(new Date(0));
        setFinishTimeCookies("finish_time", Date.now());
        removeStartTimeCookies("start_time");
        removePauseTimeCookies("pause_time");
        removeReStartTimeCookies("restart_time");
        removeReStartToken("restart_token");
        clearTimeout(interval.current);
      };
    

     

     

    현재 존재하는 문제점

     

     

    위의 코드에도 몇가지 문제점이 존재하는데, 첫째로는 비동기 처리 문제 때문에 타이머의 시간이 바로 적용이 안된다는 점이고, 두번째로는 시간이 초단위로 살짝 안맞는다는 것입니다. 이 두가지 문제점을 제외하고는 그럭저럭 돌아가고 있기 때문에 일단 이정도 선에서 마무리하고 다음 업무를 맡는 것으로 넘어갔습니다. 이번 타이머 코드는 후에 다른 프로젝트에서도 써먹을 일이 많을 것 같아서 프로젝트가 끝난 후에 리팩토링을 진행하면서 custom hook으로 만들어 놓거나 개인 라이브러리(혹은 보일러 플레이트?) 같은 걸로 만들어 정리해놓는 것이 좋겠다는 생각이 들었습니다.

     

    5. 마무리

     

    이번 타이머 문제에서 깨달은 점은 다음과 같습니다.

     

    1) 좋았던 점

     

    1. 해결하지 못한 문제를 붙들고 꾸준히 고민해보았다는 점(며칠이 걸렸는지 까먹었지만 거의 일주일 걸린 듯 합니다).

    2. 문제를 해결하기 위해 다양한 방식을 시도해보았다는 점.

    3. 그 과정에서 Date 객체에 대한 이해, react hooks 에서의 라이프 사이클을 고민해보았다는 점.

    2) 안 좋았던 점

     

    1. 한 문제에 너무 많은 시간을 투자해서 업무 효율성이 떨어졌던 점.

    2. 그 과정에서 스트레스 관리가 쉽지 않았다는 점.

    3. 작업 효율이 좋지 않다보니, 프로젝트 전체적인 관점에서 다른 팀원들에게 민폐가 되지 않았나 하는 점.

     

    다음 프로젝트를 진행하게 될 경우에는 단점을 좀 더 보완해서 안 풀리는 문제를 어떻게 하면 빨리 해결할 수 있을 것인지에 대해 고민하고 해결책을 적용하는 방식으로 업무를 해야겠습니다.

     

    이번 코드 포스팅 글이 다소 두서없고 산만함에도 불구하고 끝까지 읽어주셔서 감사합니다. 다음 포스팅에서는 좀 더 나은 글로 돌아오도록 하겠습니다.

     

     

     

    728x90

    'IT 지식' 카테고리의 다른 글

    [TIL] 동적 라우팅  (0) 2020.12.06
    [TIL] 로컬 스토리지, 세션 스토리지 그리고 쿠키  (0) 2020.12.06
    [TIL] 스키마에 대한 정리  (0) 2020.11.29
    [TIL] Date 객체에 대해 알아보자  (0) 2020.11.29
    [TIL] axios 사용법  (0) 2020.11.29

    댓글

Designed by Tistory.