본문 바로가기

3주차

[React] Custom Hook

안녕하세요. 다시 돌아온 웹 배영경입니다😵‍💫

이번에는 Custom Hook에 대해 알아보았습니다!

React로 게임 만들기를 하면서, useState와 useEffect를 많이 썼는데요, Custom Hook는 어떤것인지 살펴보겠습니다.


Custom Hook 사용 이유

사용자 정의 후크는 재사용 가능한 로직을 캡슐화하여 components를 더 깔끔하고 유지 관리하기 쉽게 만들 수 있다.
사용자 정의 후크를 생성하면 복잡한 로직을 UI에서 분리하여 구성 요소를 더 단순하게 유지하고 렌더링에 집중할 수 있다.

 

  • 다양한 구성 요소에서 상태 저장 논리를 재사용
  • 복잡한 로직을 캡슐화하여 components를 더 단순하게 만듦
  • 코드를 더 읽기 쉽고 테스트하기 쉽게 만듦

 

Custom Hook 만들기

사용자 정의 후크는 use 로 시작하도록 명명한다

예를 들어, 게임에서의 타이머 로직은 useTimer라는 사용자 정의 후크로 캡슐화될 수 있다.

 

이 후크는 다음을 관리한다.

  • 타이머의 값(시간이 얼마나 경과했는지).
  • 타이머를 시작하고 중지합니다.
  • 타이머를 재설정합니다.

이 로직을 'useTimer'에 분리하면, UI 렌더링을 할 때 'Game' 컴포넌트에 집중할 수 있다.

 

useTimer 설정

useTimer 후크는 다음과 같이 구성할 수 있다.

useStateuseEffectuseReduceruseCallback 등 Hooks 를 사용하여 원하는 기능을 구현해주고, 컴포넌트에서 사용하고 싶은 값들을 반환해주면 된다!

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

const useTimer = () => {
  const [timer, setTimer] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  // 타이머 시작
  const startTimer = useCallback(() => {
    setIsRunning(true);
  }, []);

  // 타이머 중지
  const stopTimer = useCallback(() => {
    setIsRunning(false);
  }, []);

  // 타이머 초기화
  const resetTimer = useCallback(() => {
    setTimer(0);
    setIsRunning(false);
  }, []);

  // 타이머가 작동할 때, 50ms마다 타이머 업데이트
  useEffect(() => {
    let interval = null;
    if (isRunning) {
      interval = setInterval(() => {
        setTimer((prevTimer) => +(prevTimer + 0.05).toFixed(2)); // 50ms 간격
      }, 50);
    }
   
    return () => clearInterval(interval);
  }, [isRunning]);

  return { timer, startTimer, stopTimer, resetTimer, isRunning };
};

export default useTimer;

 

Game 컴포넌트에서 'useTimer' 후크 사용

정의된 useTimer를 Game 컴포넌트에서 사용할 수 있다.
'Game'이 더 단순화되고, Game 렌더링에 더 집중할 수 있게된다.

 

import React, { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import useTimer from '../hooks/useTimer';

const Game = ({ nextNumber, setNextNumber, currentSet, setCurrentSet, resetGame }) => {
  const { timer, startTimer, stopTimer, resetTimer, isRunning } = useTimer(); // custom hook 사용

  const [numbers, setNumbers] = useState([]);

  useEffect(() => {
    setNumbers(shuffleArray(currentSet));
  }, [currentSet]);

  const shuffleArray = (array) => array.sort(() => Math.random() - 0.5);

  const handleNumberClick = (num) => {
    if (num === nextNumber) {
      if (num === 1 && !isRunning) {
        startTimer(); // 타이머 시작
      }
      setNextNumber((prev) => prev + 1);

      const newNumbers = [...numbers];
      const index = newNumbers.indexOf(num);
      if (nextNumber <= 9) {
        newNumbers[index] = getNextNumberInRange(10, 18);
      } else if (nextNumber === 18) {
        stopTimer(); // 게임이 끝났을 때 타이머 중지
        alert(`게임 끝! 기록: ${timer.toFixed(2)} 초`);
        resetGame();
      } else {
        newNumbers[index] = null;
      }
      setNumbers(newNumbers);
    }
  };

  const getNextNumberInRange = (start, end) => {
    const availableNumbers = Array.from({ length: end - start + 1 }, (_, i) => start + i).filter(n => !numbers.includes(n));
    return shuffleArray(availableNumbers)[0];
  };

  return (
    <GameBoard>
      <h2>Next Number: {nextNumber}</h2>
      <p>Time: {timer.toFixed(2)} seconds</p>
      <GameButtonPlace>
        {numbers.map((num, index) => (
          num !== null ? (
            <GameButton key={index} onClick={() => handleNumberClick(num)}>
              {num}
            </GameButton>
          ) : (
            <EmptyButton key={index} />
          )
        ))}
      </GameButtonPlace>
    </GameBoard>
  );
};


export default Game;

 

 


 

Custom Hook를 사용해보기 위해 예를 든 것이므로, Custom Hook를 사용하기에 완전히 적합한 상황이 아닐 수 있다.