๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Front-end

Timer Interval ์ง€์—ฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•

 

์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ์ง„ํ–‰ํ•˜๋˜ ์ค‘, ๊ธฐ์กด ๊ตฌํ˜„๋œ ํƒ€์ด๋จธ์—์„œ ์ธํ„ฐ๋ฒŒ ์‹œ์ ์ด ์ง€์—ฐ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์ƒ๊ฐํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด ์ž‘์„ฑ๋œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

AS-IS. ์‹œ๊ฐ„์„ ์ง์ ‘ ๊ณ„์‚ฐ, setInterval๋กœ 1์ดˆ๋งˆ๋‹ค ๊ฐฑ์‹ 

'use client';

import { useEffect, useState } from 'react';
import { atomWithStorage } from 'jotai/utils';
import { useAtom } from 'jotai';

const startTimeAtom = atomWithStorage<number>('inboundProcessStartTime', Date.now());

export default function Timer() {
  const [startTime, setStartTime] = useAtom(startTimeAtom);
  const [elapsedTime, setElapsedTime] = useState(() => Math.floor((Date.now() - startTime) / 1000));

  useEffect(() => {
    const updateTimer = () => {
      setElapsedTime(Math.floor((Date.now() - startTime) / 1000));
    };

    updateTimer(); // ์ตœ์ดˆ ์‹คํ–‰
    const intervalId = setInterval(updateTimer, 1000);

    return () => clearInterval(intervalId);
  }, [startTime]);

  const resetTimer = () => {
    const newStartTime = Date.now();
    setStartTime(newStartTime);
    setElapsedTime(0);
  };

  const formatTime = (seconds: number) => {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const remainingSeconds = seconds % 60;
    return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
  };

  return (
    <div>
      <p>๊ฒฝ๊ณผ ์‹œ๊ฐ„: {formatTime(elapsedTime)}</p>
      <button onClick={resetTimer}>ํƒ€์ด๋จธ ์ดˆ๊ธฐํ™”</button>
    </div>
  );
}
  • `inboundProcessStartTime` ๋ณ€๊ฒฝ ์‹œ ํ•ญ์ƒ ํ˜„์žฌ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ๊ฒฝ๊ณผ ์‹œ๊ฐ„์„ ๊ณ„์‚ฐํ•˜๋ฏ€๋กœ, ์ธํ„ฐ๋ฒŒ ์‹œ์ž‘/์ทจ์†Œ๋กœ ์ธํ•œ ์‹œ๊ฐ„ ์ง€์—ฐ ๋ฌธ์ œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
    • ํ˜„์žฌ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์—ฐ์‚ฐ์„ ํ•˜์ง€๋งŒ setInterval๋กœ ํ™”๋ฉด ๋ Œ๋”๋ง์ด ๋˜๋ฉฐ ์‹œ๊ฐ„ ์ง€์—ฐ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

 

TO-BE. requestAnimationFrame์„ ํ†ตํ•ด ์œ ์—ฐํ•œ ํ™”๋ฉด ๊ฐฑ์‹ 

setInterval์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , requestAnimationFrame์„ ํ™œ์šฉํ•ด ๊ฐฑ์‹  ์ฃผ๊ธฐ๊ฐ€ ๊ณ ์ •๋œ 1์ดˆ๋ณด๋‹ค ๋” ์œ ์—ฐํ•˜๊ฒŒ ๋™์ž‘ํ•˜์—ฌ ์ฆ‰์‹œ ์‹œ๊ฐ„์„ ์—…๋ฐ์ดํŠธํ•˜๋„๋ก ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

"use client";

import { useAtom } from "jotai";
import { atomWithStorage } from "jotai/utils";
import { useEffect, useState } from "react";

const startTimeAtom = atomWithStorage<number>(
  "inboundProcessStartTime",
  Date.now()
);

export default function Timer() {
  const [startTime, setStartTime] = useAtom(startTimeAtom);
  const [elapsedTime, setElapsedTime] = useState(() =>
    Math.floor((Date.now() - startTime) / 1000)
  );

  useEffect(() => {
    let animationFrameId: number;

    const updateTimer = () => {
      setElapsedTime(Math.floor((Date.now() - startTime) / 1000));
      animationFrameId = requestAnimationFrame(updateTimer);
    };

    updateTimer();

    return () => cancelAnimationFrame(animationFrameId);
  }, [startTime]);

  const resetTimer = () => {
    const newStartTime = Date.now();
    setStartTime(newStartTime);
    setElapsedTime(0);
  };

  const formatTime = (seconds: number) => {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const remainingSeconds = seconds % 60;
    return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(
      2,
      "0"
    )}:${String(remainingSeconds).padStart(2, "0")}`;
  };

  return (
    <div>
      <p>๊ฒฝ๊ณผ ์‹œ๊ฐ„: {formatTime(elapsedTime)}</p>
      <button onClick={resetTimer}>ํƒ€์ด๋จธ ์ดˆ๊ธฐํ™”</button>
    </div>
  );
}
  • inboundProcessStartTime ๋ณ€๊ฒฝ ์ฆ‰์‹œ ํ™”๋ฉด์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.
  • requestAnimationFrame ์„ ํ™œ์šฉํ•˜์—ฌ ๊ฐฑ์‹  ์ฃผ๊ธฐ๊ฐ€ ๊ณ ์ •๋œ 1์ดˆ๋ณด๋‹ค ๋” ์œ ์—ฐํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์ž์—ฐ์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค.
    • requestAnimationFrame:
      • requestAnimationFrame์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™”๋ฉด ๊ฐฑ์‹  ์ฃผ๊ธฐ์— ๋งž์ถฐ์„œ ์ฝœ๋ฐฑํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, updateTimer ํ•จ์ˆ˜๋Š” ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆด ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
      • updateTimer๋Š” setElapsedTime์„ ์‚ฌ์šฉํ•ด elapsedTime์„ ๊ณ„์† ๊ฐฑ์‹ ํ•˜๊ณ , requestAnimationFrame(updateTimer)๋กœ ์ž์‹ ์„ ๋‹ค์‹œ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ํƒ€์ด๋จธ๊ฐ€ ๊ณ„์† ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค.
    • updateTimer ํ•จ์ˆ˜๋Š” requestAnimationFrame์„ ํ˜ธ์ถœํ•˜์—ฌ ์ž์‹ ์„ ๊ณ„์† ์‹คํ–‰ํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ํ•œ ๋ฒˆ updateTimer๊ฐ€ ์‹คํ–‰๋˜๋ฉด, ๊ทธ ๋‹ค์Œ์—๋Š” ๋‹ค์‹œ requestAnimationFrame(updateTimer)๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด์„œ ๊ณ„์†ํ•ด์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ™”๋ฉด์„ ์ƒˆ๋กœ ๊ทธ๋ฆด ๋•Œ๋งˆ๋‹ค updateTimer๊ฐ€ ํ˜ธ์ถœ๋˜๋ฏ€๋กœ, ํƒ€์ด๋จธ๋Š” ํ™”๋ฉด ๊ฐฑ์‹  ์ฃผ๊ธฐ๋งˆ๋‹ค ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
 

Window: requestAnimationFrame() method - Web API | MDN

window.requestAnimationFrame() ๋ฉ”์„œ๋“œ๋Š” ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ ์ˆ˜ํ–‰ํ•˜๊ธฐ๋ฅผ ์›ํ•˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์•Œ๋ฆฌ๊ณ  ๋‹ค์Œ ๋ฆฌํŽ˜์ธํŠธ ๋ฐ”๋กœ ์ „์— ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์—…๋ฐ์ดํŠธํ•  ์ง€์ •๋œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.

developer.mozilla.org

 

 

 

 

 

 

  • references:
 

์›น์—์„œ ์ •ํ™•ํ•œ ํƒ€์ด๋จธ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€?

How to make accurate timer in Web

medium.com

 

 

Why Javascript timer is unreliable, and how can you fix it

If you are a Javascript developer, at some point in your career, you must have used setTimeout or setInterval. They are extremely handy if…

abhi9bakshi.medium.com