메인 콘텐츠로 바로가기
노트 목록

튜토리얼 — 디지털 시계 만들기

김성박751
목차8

카운트는 버튼을 눌러야 바뀌었죠. 이번엔 버튼 없이 저절로 바뀌는 걸 만들어 봅니다. 1초마다 똑딱이는 시계입니다. useState로 시각을 담고, useEffect로 타이머를 켜고, 앞 절에서 배운 정리 함수로 타이머를 끕니다.


개념

시계가 움직이려면 두 가지가 필요합니다.

  • 지금 몇 시인지 기억할 곳 → useState
  • 1초마다 시각을 새로 고치는 장치 → useEffect 안의 setInterval

setInterval은 딱 "켜면 끄기"가 짝인 효과입니다. 켜 두기만 하고 끄지 않으면, 시계 컴포넌트가 사라진 뒤에도 타이머가 계속 돌면서 사라진 컴포넌트의 상태를 바꾸려 합니다. 그래서 정리 함수에서 clearInterval로 꼭 꺼 줍니다.


함께 해보기

1단계 — 시계 컴포넌트 만들기

components/Clock.tsx 파일을 만듭니다.

"use client"; import { useState, useEffect } from "react"; export default function Clock() { // 1. 지금 시각을 상태로 — 처음 값은 "만들어진 순간" const [now, setNow] = useState(new Date()); // 2. 타이머 켜기 (마운트 때 한 번) useEffect(() => { const timerId = setInterval(() => { setNow(new Date()); // 1초마다 새 시각으로 교체 → 다시 그려짐 }, 1000); // 3. 타이머 끄기 (정리 함수) return () => { clearInterval(timerId); console.log("정리 실행: 타이머를 껐습니다"); }; }, []); // 의존성 빈 배열 → 켜기 한 번, 끄기 한 번 return <h1>{now.toLocaleTimeString("ko-KR")}</h1>; }

여기서 의존성 배열이 빈 배열 [] 인 게 핵심입니다. 카운트 예제에서는 [count]라 count가 바뀔 때마다 효과가 다시 돌았죠. 빈 배열은 "바뀔 의존성이 없다"는 뜻 — 효과는 마운트 때 딱 한 번 켜지고, 정리는 언마운트 때 딱 한 번 실행됩니다.

setNow로 상태를 바꾸면 컴포넌트가 다시 그려집니다. 그래서 1초마다 화면의 시각이 갱신돼요. 타이머가 컴포넌트를 다시 실행시키는 게 아니라, 상태 변경이 다시 그리는 겁니다.

2단계 — 페이지에 올리기

app/clock/page.tsx를 만듭니다.

"use client"; import { useState } from "react"; import Clock from "@/components/Clock"; export default function ClockPage() { const [showClock, setShowClock] = useState(true); return ( <main style={{ padding: 24 }}> <h2>디지털 시계</h2> {/* showClock이 true일 때만 시계를 그림 */} {showClock && <Clock />} <button onClick={() => setShowClock(!showClock)}> {showClock ? "시계 숨기기" : "시계 보이기"} </button> </main> ); }

시계를 숨겼다 보였다 하는 버튼을 일부러 넣었습니다. 정리 함수가 실행되는 순간을 눈으로 보려고요.

3단계 — 확인

/clock을 열고 콘솔을 봅니다.

  • 시각이 1초마다 바뀝니다 ✅
  • "시계 숨기기" 를 누르면 → Clock이 화면에서 사라지고(언마운트), 콘솔에 정리 실행: 타이머를 껐습니다가 찍힙니다.
  • "시계 보이기" 를 다시 누르면 → Clock이 새로 마운트되고 타이머가 다시 켜집니다.
(시계 보임 — 타이머 켜짐, 똑딱똑딱) 정리 실행: 타이머를 껐습니다 ← "숨기기" 누름 (언마운트) (시계 사라짐 — 타이머 꺼짐, 조용함) 정리 실행: 타이머를 껐습니다 ← "보이기" 후 다시 "숨기기"

확인 ✅ — 정리 함수가 언마운트 때 실행되는 걸 직접 봤습니다. 만약 clearInterval을 빼먹었다면, 숨긴 뒤에도 타이머가 계속 돌면서 콘솔에 경고가 떴을 겁니다 ("사라진 컴포넌트의 상태를 바꾸려 한다").

개발 모드에서는 마운트 직후 정리 실행이 한 번 더 보일 수 있습니다. 앞 절에서 본 그 검사예요 — 정리 함수를 제대로 짰는지 React가 확인해 주는 겁니다. 놀라지 마세요.


정리

  • useState는 무엇을 기억할지(현재 시각), useEffect는 언제 무엇을 할지(1초마다 갱신)를 맡습니다.
  • setInterval로 켠 타이머는 정리 함수의 clearInterval로 반드시 꺼 줍니다.
  • 의존성 배열이 [] 면 → 효과는 마운트 때 한 번, 정리는 언마운트 때 한 번.
  • 화면이 갱신되는 진짜 이유는 타이머가 아니라 setNow(상태 변경) 입니다.

연습 거리

시계를 다 만들었다면 이어서 해 보세요.

  1. now.toLocaleDateString("ko-KR")로 날짜도 같이 표시하기
  2. 1000ms를 100ms로 바꿔 보고, 밀리초까지 보이게 하기
  3. console.log로 "효과 실행"도 찍어, 켜기/끄기 짝이 맞는지 카운트 예제처럼 확인하기

댓글

불러오는 중...

댓글을 작성하려면 이 필요합니다.