[웹으로 낙서하기#2]디지털 시계의 시간을 흐르게 해보자!

2020. 7. 27. 18:30웹돌이 단편선

2020/07/23 - [웹돌이 단편선] - [웹으로 낙서하기#1]자바스크립트를 사용해서 웹 브라우저에 디지털 시계를 올려놔보자!

 

[웹으로 낙서하기#1]자바스크립트를 사용해서 웹 브라우저에 디지털 시계를 올려놔보자!

웹으로 낙서하기 시리즈를 여는 글로써, 자바스크립트를 사용해 디지털 시계를 만들어보겠습니다. 필요한 기능 현재 시간을 HH:mm:ss 포맷으로 보여준다. 디지털 시계다. 1초마다 초가 흐른다. 필�

exilee20c.tistory.com

이전 글에서는 디지털 시계 외형을 만들어봤다. 이번 글에서는 시계답게 1초마다 시간이 흐르는 것을 보여주는 간단한 코딩을 하겠다.


이전 글의 DigitalClock 컴포넌트로부터 시작한다.

시계는 그 자체로 시간의 흐름을 갖고 있기 때문에 스스로 state를 갖고 있다.

import React, { useState } from "react";
import Digit from "../../atoms/Digit";

function getZeroPadString(number) {
  return number.toString().replace(/([0-9])?([0-9])$/g, function (_, $1, $2) {
    return `${$1 || 0}${$2}`;
  });
}

function mapDigit(digit, i) {
  return <Digit key={i} value={digit} />;
}

function DigitalClock() {
  const [date, setDate] = useState(new Date());

  const HH = getZeroPadString(date.getHours());
  const mm = getZeroPadString(date.getMinutes());
  const ss = getZeroPadString(date.getSeconds());

  return (
    <div>
      {Array.from(HH).map(mapDigit)}:{Array.from(mm).map(mapDigit)}:
      {Array.from(ss).map(mapDigit)}
    </div>
  );
}

export default DigitalClock;

마운트시 1초마다 시간을 갱신하는 훅을 추가해 보겠다.


function DigitalClock() {
  const [date, setDate] = useState(new Date());
  const refreshDate = useRef(() => setDate(new Date()));

  useEffect(() => {
    const interval = setInterval(refreshDate.current, 1000);
    return () => clearInterval(interval);
  }, []);

  const HH = getZeroPadString(date.getHours());
  const mm = getZeroPadString(date.getMinutes());
  const ss = getZeroPadString(date.getSeconds());

  return (
    <div>
      {Array.from(HH).map(mapDigit)}:{Array.from(mm).map(mapDigit)}:
      {Array.from(ss).map(mapDigit)}
    </div>
  );
}

시간이 흐른다.


이때 밀리초 단위까지 시간을 보여주면 꽤나 불편한 상황을 볼수 있다.

520밀리초가 거슬린다...

그래서 초기 렌더링시 520밀리초가 흐른 상태라면 1000밀리초로 setInterval 하는것이 아닌, 1000밀리초 - 520밀리초 = 480밀리초 후에 타임 아웃을 걸고, 매 틱마다 1000 밀리초 - x밀리초만큼의 다음번 타임아웃을 재귀적으로 설정해봤다.

import React, { useState, useRef, useEffect } from "react";
import Digit from "../../atoms/Digit";

function getZeroPadString(number, stack = 1) {
  const remainStack = stack - 1;
  const fn =
    remainStack > 0 ? (i) => getZeroPadString(i, remainStack) : (i) => i;

  return number.toString().replace(/([0-9]*)?([0-9])$/g, function (_, $1, $2) {
    return `${fn($1 || 0)}${$2}`;
  });
}

function mapDigit(digit, i) {
  return <Digit key={i} value={+digit} />;
}

function DigitalClock() {
  const [date, setDate] = useState(new Date());
  const dateRef = useRef(date);

  const refreshDate = useRef(() => {
    const newDate = new Date();
    setDate(newDate);
    dateRef.current = newDate;
    nextTick.current();
  });

  const nextTick = useRef(() =>
    setTimeout(refreshDate.current, 1000 - dateRef.current.getMilliseconds())
  );

  useEffect(() => {
    const timeout = nextTick.current();
    return () => clearTimeout(timeout);
  }, []);

  const HH = getZeroPadString(date.getHours());
  const mm = getZeroPadString(date.getMinutes());
  const ss = getZeroPadString(date.getSeconds());
  const SSS = getZeroPadString(date.getMilliseconds(), 2);

  return (
    <div>
      {Array.from(HH).map(mapDigit)}:{Array.from(mm).map(mapDigit)}:
      {Array.from(ss).map(mapDigit)}:{Array.from(SSS).map(mapDigit)}
    </div>
  );
}

export default DigitalClock;

코드는 위와 같았고, 다음과 같은 결과가 보여졌다.

새로고침한 시점에 각각 777, 521밀리초였으나 빠르게 0+-15밀리초 범위로 오차를 좁혔다.

이 이상 좁힐수 없는 오차는 다음번 틱을 재귀적으로 호출하기 위한 연산 시간만큼의 지연시간 때문일 것인데, 이 시간도 줄일수 있는 방법은 추후에 고민을 해봐야겠다. 당장은 오차가 크지 않고, 초단위까지만 보여줘도 충분하기 때문에 시분초까지만 출력을 하며 마치겠다.

import React, { useState, useRef, useEffect } from "react";
import Digit from "../../atoms/Digit";

function getZeroPadString(number, stack = 1) {
  const remainStack = stack - 1;
  const fn =
    remainStack > 0 ? (i) => getZeroPadString(i, remainStack) : (i) => i;

  return number.toString().replace(/([0-9]*)?([0-9])$/g, function (_, $1, $2) {
    return `${fn($1 || 0)}${$2}`;
  });
}

function mapDigit(digit, i) {
  return <Digit key={i} value={+digit} />;
}

function DigitalClock() {
  const [date, setDate] = useState(new Date());
  const dateRef = useRef(date);

  const refreshDate = useRef(() => {
    const newDate = new Date();
    setDate(newDate);
    dateRef.current = newDate;
    nextTick.current();
  });

  const nextTick = useRef(() =>
    setTimeout(refreshDate.current, 1000 - dateRef.current.getMilliseconds())
  );

  useEffect(() => {
    const timeout = nextTick.current();
    return () => clearTimeout(timeout);
  }, []);

  const HH = getZeroPadString(date.getHours());
  const mm = getZeroPadString(date.getMinutes());
  const ss = getZeroPadString(date.getSeconds());

  return (
    <div>
      {Array.from(HH).map(mapDigit)}:{Array.from(mm).map(mapDigit)}:
      {Array.from(ss).map(mapDigit)}
    </div>
  );
}

export default DigitalClock;