귀여운 눈높이에서 작성된, 🐰

하루 한 걸음씩, 성장 하는 중 입니다 🫶🏻

Studying/미니 프로젝트

미니프로젝트의 두번째 기능 - 시계 구현 해보기

creamymood 2025. 4. 15. 16:52

째깍째깍 ~ 


 

1. 큰 흐름 생각해보기

2. 상세 설명이 달린 전체 코드

3. 코드 상세 분석

4. useEffect로 시간 관리하는 이유


큰 흐름 생각해보기

리액트로 실시간 시계를 만들려면, 먼저 시간 데이터를 상태로 관리하고 그 상태를 주기적으로 업데이트하는 방식으로 접근하면 좋다.

코드를 짜기 전에 필요한 구성 요소들을 생각해보자면:

  1. 상태 관리 (state management): 시간을 저장하는 상태를 만들어야 함.
  2. setInterval로 실시간 시간 업데이트: 매초마다 시간을 업데이트하는 코드가 필요.
  3. 시간 형식 맞추기: 시간은 "00.00.00" 형식으로 표시되어야 하므로, 이를 포맷팅해야 함.
  4. 렌더링: UI에 시간을 표시하는 부분.

우선 순위 제안:

  1. 상태 관리 - 시간을 추적할 수 있는 상태가 필요하니까 이게 첫 번째로 중요함.
  2. setInterval로 시간 업데이트 - 상태를 업데이트하는 방법과 시간이 자동으로 갱신되도록 하는 부분.
  3. 시간 형식 맞추기 - 시간을 출력할 때, "00.00.00" 형식으로 바꾸는 작업.
  4. 렌더링 - 시간 값을 화면에 렌더링하는 부분은 상태 업데이트가 제대로 이루어지면 자연스럽게 처리될 것임.

전체코드

전체 코드는 다음과 같다.

import React, { useState, useEffect } from 'react';

function App() {
  return (
    <div>
      <Clock /> {/* Clock 컴포넌트를 화면에 렌더링 */}
    </div>
  );
}

function Clock() {
  // 1. 시간 상태 관리 (00.00.00 형식의 시간)
  const [time, setTime] = useState("00.00.00");

  // 2. setInterval을 사용하여 시간 업데이트
  useEffect(() => {
    // 시간을 1초마다 갱신하는 함수
    const interval = setInterval(() => {
      const currentTime = new Date();
      
      // 3. 시간을 "00.00.00" 형식으로 포맷팅
      const hours = String(currentTime.getHours()).padStart(2, '0');
      const minutes = String(currentTime.getMinutes()).padStart(2, '0');
      const seconds = String(currentTime.getSeconds()).padStart(2, '0');

      // 4. 상태 업데이트: 시간을 "00.00.00" 형식으로 설정
      setTime(`${hours}.${minutes}.${seconds}`);
    }, 1000); // 1000ms = 1초마다 갱신

    // 5. 컴포넌트가 언마운트될 때 setInterval을 정리 (메모리 누수 방지)
    return () => clearInterval(interval);
  }, []); // 빈 배열을 사용하여 컴포넌트가 마운트될 때만 실행됨

  return (
    // 6. 시계 UI를 렌더링 (시간 상태를 표시)
    <div>
      <h1>{time}</h1>
    </div>
  );
}

export default App;

각 부분에 대한 설명과 주석 추가:

  1. 상태 관리 (useState):
    • const [time, setTime] = useState("00.00.00");는 시간을 저장할 상태를 생성하는 부분이야. 여기서는 초기 값을 "00.00.00"로 설정했어.
  2. setInterval로 시간 업데이트:
    • useEffect 훅은 컴포넌트가 마운트될 때 실행되고, 그 안에서 setInterval을 사용하여 매초 시간을 업데이트해. 이 때 currentTime 객체를 생성해서 현재 시간을 받아오고 있어.
    • 인터벌로 관리 하는 이유, 아래 블로그 참고
    • https://creamymood.tistory.com/75
  3. 시간 형식 맞추기:
    • String(currentTime.getHours()).padStart(2, '0'); 처럼, getHours, getMinutes, getSeconds 메서드를 사용해 시, 분, 초를 각각 구해. 그 후 padStart(2, '0')로 두 자리 숫자가 되도록 만들어.
  4. 상태 업데이트:
    • setTime(${hours}.${minutes}.${seconds}); 이 부분은 우리가 구한 시, 분, 초를 00.00.00 형식으로 변환해서 상태를 업데이트하는 부분이야.
  5. 컴포넌트 언마운트 시 setInterval 정리:
    • return () => clearInterval(interval); 이 부분은 컴포넌트가 언마운트될 때 setInterval이 계속 동작하는 것을 방지하기 위해 정리하는 코드야.
  6. 렌더링:
    • return <div><h1>{time}</h1></div>; 시계 UI는 <h1> 태그 안에 현재 시간을 표시하고 있어.

 

 


코드 상세 분석

 

 

const [time, setTime] = useState("00.00.00");

여기서는 useState 훅을 사용해서 time이라는 상태 변수를 선언하고, 초기값을 "00.00.00"으로 설정했다

setTime은 이 상태를 변경할 때 사용하는 함수야.

즉, 이 time 상태는 시계의 현재 시간을 저장하고, setTime을 통해 시간을 갱신할 예정이다.

 


이제 계속해서 시간을 갱신하는 로직을 작성해보자

const interval = setInterval(() => {
  • setInterval은 지정된 시간 간격(여기서는 1초)마다 주어진 함수를 반복해서 실행하는 함수다.
  • interval은 이 setInterval을 통해 반환되는 ID 값을 저장해. 이 ID 값은 나중에 clearInterval로 사용해서 인터벌을 중지할 수 있다.

 

const interval = setInterval(() => {
  const currentTime = new Date();
  const hours = String(currentTime.getHours()).padStart(2, '0');
  const minutes = String(currentTime.getMinutes()).padStart(2, '0');
  const seconds = String(currentTime.getSeconds()).padStart(2, '0');
  setTime(`${hours}.${minutes}.${seconds}`);
}, 1000); // 1000ms = 1초마다 갱신
  • setInterval을 사용하여 매 1초마다 실행되는 함수를 설정했어. 그 함수 안에서는 new Date()로 현재 시간을 가져오고, getHours(), getMinutes(), getSeconds()를 사용해서 각각 시간, 분, 초를 구해. padStart(2, '0')로 1자리 숫자는 앞에 0을 추가해서 두 자리로 만들어줘.
  • 그 후, setTime을 사용해 time 상태를 새로운 시간 값으로 갱신해. 이 시간은 "00.00.00" 형식으로 표시돼.

그리고 난 뒤, 마지막으로 setInterval을 정리하는 부분을 추가할 거야.

 

 

아래에서 위 코드를 자세하게 살펴보자.

코드 설명:

  1. setInterval 함수:
    • setInterval 함수는 주어진 간격(밀리초 단위)마다 특정 작업을 반복해서 실행하는 함수입니다.
    • 여기서는 1000 밀리초(즉, 1초)마다 현재 시간을 가져오는 작업을 수행하도록 설정되어 있습니다.
    const interval = setInterval(() => { 
        // ... 여기에 반복할 코드
    }, 1000);  // 1초마다 반복
    
  2. new Date() 객체:
    • new Date()는 현재 날짜와 시간을 나타내는 JavaScript의 Date 객체를 생성합니다.
    • 이 객체에서 getHours(), getMinutes(), getSeconds() 메서드를 사용하여 현재 시각을 각각 시간, 분, 초 단위로 얻을 수 있습니다.
    const currentTime = new Date();
    
  3. getHours(), getMinutes(), getSeconds():
    • currentTime.getHours(): 현재 시간을 24시간 형식으로 가져옵니다. (0~23 사이의 값)
    • currentTime.getMinutes(): 현재 분을 가져옵니다. (0~59 사이의 값)
    • currentTime.getSeconds(): 현재 초를 가져옵니다. (0~59 사이의 값)
    const hours = String(currentTime.getHours()).padStart(2, '0');
    const minutes = String(currentTime.getMinutes()).padStart(2, '0');
    const seconds = String(currentTime.getSeconds()).padStart(2, '0');
    

이 코드에서 String(currentTime.getHours())는 getHours() 메서드의 결과인 숫자 값을 문자열로 변환하는 역할을 합니다.

 

currentTime.getHours()는 숫자를 반환합니다. 예를 들어, 시간이 9시라면 getHours()는 9를 반환하고, 15시라면 15를 반환합니다.

그러나 padStart() 메서드는 문자열에만 적용될 수 있습니다.

 

padStart()는 문자열의 길이를 맞추기 위해 앞에 특정 문자를 추가하는 메서드입니다. 예를 들어, padStart(2, '0')는 두 자리 수로 만들기 위해, 하나의 자리수인 숫자 앞에 '0'을 추가합니다.

 

따라서, padStart()를 사용하기 전에 getHours()로 얻은 숫자를 문자열로 변환해야 합니다. 그렇지 않으면 padStart()가 제대로 동작하지 않기 때문에, String()을 사용해 숫자를 문자열로 변환한 후, 그 문자열에 padStart()를 적용하는 것입니다.

예시:

const hours = String(currentTime.getHours()).padStart(2, '0');
  • 만약 getHours()가 9를 반환했다면, String(9)는 '9'가 되고, padStart(2, '0')는 '09'로 변환됩니다.
  • 만약 getHours()가 15를 반환했다면, String(15)는 '15'가 되고, padStart(2, '0')는 '15' 그대로 유지됩니다.

 

 

  1. padStart(2, '0'):
    • padStart는 문자열의 길이가 지정한 길이보다 짧을 경우, 앞에 지정한 문자(여기서는 '0')를 추가하여 길이를 맞추는 메서드입니다.
    • 예를 들어, 시간이 9시라면 09로 표시되도록 padStart(2, '0')를 사용하여 두 자릿수 형식으로 만듭니다.
    const hours = String(currentTime.getHours()).padStart(2, '0');
    
    ***  padStart(3, '0')를 사용하면 문자열의 길이가 3보다 짧을 경우, 앞에 '0'을 추가해서 길이를 맞추게 됩니다.

예를 들어, 숫자 9를 padStart(3, '0')로 처리하면, "009"로 변환됩니다.

이 방식은 항상 지정한 길이에 맞춰서 문자열의 앞부분에 지정한 문자를 추가해줍니다!


setTime 함수 호출:

  • setTime은 주어진 인자(${hours}.${minutes}.${seconds})로 시간을 표시하도록 설정된 함수입니다.
  • 이 코드에서는 setTime 함수가 실제로 어떻게 정의되어 있는지 보여지지 않지만, 시간 문자열을 화면에 갱신하는 역할을 할 것으로 추측됩니다. 예를 들어, 상태 관리나 UI 업데이트를 위한 함수일 수 있습니다.
setTime(`${hours}.${minutes}.${seconds}`);

이 코드에서 $는 템플릿 리터럴(template literal)이라는 JavaScript 문법을 나타냅니다. 템플릿 리터럴은 문자열을 다룰 때, 변수나 표현식을 쉽게 포함할 수 있게 해줍니다. 예를 들어, 문자열 안에 변수를 삽입할 때 ${} 구문을 사용합니다.

예를 들어:

let hours = 10;
let minutes = 30;
let seconds = 45;
console.log(`The time is ${hours}:${minutes}:${seconds}`);

위 코드에서 The time is ${hours}:${minutes}:${seconds}는 The time is 10:30:45로 출력됩니다.

따라서, setTime(${hours}.${minutes}.${seconds});는 변수 hours, minutes, seconds의 값을 사용하여 동적으로 시간을 설정하려는 코드로 이해할 수 있습니다. ${hours}.${minutes}.${seconds}는 hours, minutes, seconds 값이 각각 삽입된 문자열을 생성하게 됩니다.

정리하면, $는 문자열 안에서 변수를 삽입하는 데 사용되며, ${} 구문을 통해 표현식을 넣을 수 있습니다.


전체적인 흐름:

  • setInterval은 1초마다 currentTime을 가져오고, 그 시간 정보를 두 자릿수로 포맷팅한 후 setTime을 호출하여 화면에 표시합니다. 결과적으로 화면에 매초 현재 시간이 hh.mm.ss 형식으로 갱신됩니다.

예시:

만약 현재 시간이 9:5:2라면, setInterval은 매초 09.05.02로 화면을 갱신할 것입니다.


마지막으로 !

return () => clearInterval(interval);
  • useEffect 안에서 반환된 함수는 컴포넌트가 언마운트될 때 실행돼. 여기서는 clearInterval(interval)을 사용해서 setInterval로 설정된 반복 실행을 멈추는 역할을 해. 이를 통해 메모리 누수를 방지할 수 있어.

이제 마무리하자!


 

왜 **useEffect**로 굳이 시간을 관리할까?

 

 

사실, **useEffect**는 상태 변경이나 외부 이벤트(예: 타이머, API 호출)와 같은 **부수 효과(side effects)**를 관리하는 데 사용돼. 시계를 만드는 데 useEffect를 사용하는 이유는 실시간으로 시간을 업데이트해야 하기 때문이야.

왜 useEffect로 시간을 관리하는가?

  1. 실시간 갱신 (타이머 기능):
    • 시계처럼 실시간으로 업데이트되는 값이 필요할 때 useEffect를 사용해 타이머를 설정할 수 있어.
    • setInterval을 사용하여 1초마다 상태를 업데이트하려면 컴포넌트가 렌더링된 후에 타이머를 설정해야 하기 때문에 useEffect가 적합해.
    • useEffect는 컴포넌트가 마운트되었을 때(렌더링 후) 실행되므로, 타이머를 설정하는 데 이상적인 장소야.
  2. 클린업 (메모리 누수 방지):
    • useEffect는 컴포넌트가 언마운트될 때(화면에서 사라질 때) 클린업 함수를 실행할 수 있어.
    • 타이머는 언제 끝날지 모르는 부수 효과이므로, 만약 컴포넌트가 화면에서 사라지더라도 타이머가 계속 돌아가면 메모리 누수가 발생할 수 있어.
    • clearInterval을 통해 타이머를 제거할 수 있어, 컴포넌트가 언마운트되면 타이머를 종료시킬 수 있기 때문에 메모리 누수를 방지할 수 있어.
  3. 상태 변경의 타이밍:
    • useState는 상태를 설정하지만, 상태 변경을 트리거하기 위한 타이밍을 정확하게 조절하려면 useEffect가 필요해.
    • 예를 들어, 1초마다 상태를 갱신하고 그 갱신된 값을 화면에 보여주려면 상태 업데이트를 외부 타이머를 통해 해야 하므로 useEffect에서 관리하는 게 맞아.

useEffect 없이 시간을 관리하는 경우?

useEffect 없이도 시간을 관리할 수 있지만, 부수 효과를 관리하는 다른 방법이 필요해. 예를 들어, 컴포넌트 외부에서 setInterval을 사용하고, 그 값을 직접 상태에 업데이트하는 방식이 있지만, 이럴 경우:

  • 타이머 클린업을 직접 해야 해.
  • 렌더링 타이밍을 맞추는 데 어려움이 있을 수 있어.

따라서 useEffect를 사용하면, 리액트가 상태 업데이트와 렌더링 사이의 타이밍을 관리해 주고, 클린업도 자동으로 처리해 주니까 더 깔끔하고 안정적인 방법이야.

간단히 요약:

  • 실시간 업데이트가 필요하고, 타이머외부 이벤트(API, setInterval 등)를 관리해야 할 때 useEffect가 사용돼.
  • 클린업을 통해 리소스를 잘 정리할 수 있어.
  • 리액트가 상태 변경과 렌더링을 동기화하고, 부수 효과를 잘 관리하도록 도와줘.

그래서 시계처럼 실시간으로 시간을 갱신하는 경우에는 useEffect가 필요하고, 타이머를 설정하는 데 필수적인 훅이야!


시간을 구현 하는 다른 방법도 있다.

아래 참고하여 읽어보기

 

ChatGPT - 코드 수정 요청

Shared via ChatGPT

chatgpt.com


출처 : 챗지피티, 구글링