[TIL]20250410

김민석·2025년 4월 10일
post-thumbnail

오늘 목표

  • 수업 전 7시 기상 후 운동(O)
  • 수업 내용 모르는 거 및 공부내용 정리(O)
  • 프로젝트 진행 해보기(O)

공부내용

fetch 함수를 다른 파일에 따로 분리해서 가져오자

useEffect 안에 fetch를 직접 다 쓰는 경우

useEffect(() => {
  fetch('https://api.example.com/data')
    .then((res) => res.json())
    .then((data) => setData(data))
    .catch((err) => console.error(err));
}, []);

코드가 useEffect 안에 다 몰려 있음
여러 API를 호출하거나, 로직이 복잡해지면 가독성 떨어짐
테스트나 디버깅이 어려움

분리해서 쓰는 경우

export const fetchData = async () => {
  const response = await fetch('https://api.example.com/data');
  if (!response.ok) {
    throw new Error('네트워크 응답에 문제가 있습니다');
  }
  return await response.json();
};
import { useEffect, useState } from 'react';
import { fetchData } from './api';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const getData = async () => {
      try {
        const result = await fetchData();
        setData(result);
      } catch (error) {
        console.error(error);
      }
    };

    getData();
  }, []);

  return <div>{JSON.stringify(data)}</div>;
};

가독성 향상 : useEffect 안이 간결해서 로직 파악이 쉬움
코드 재사용성 : 다른 컴포넌트에서도 같은 API 함수 재사용 가능
유지보수 편리 : API 변경 시 해당 JS 파일만 수정하면 됨

가위바위보 게임 실습

현재 코드에 대한 요약

코드를 다시 구현하게 된 이유는 state를 과도하게 사용했기 때문입니다.
원래는 props로만 넘겨도 충분한 데이터를 state로 관리하다 보니 코드가 복잡해졌고,
공통으로 사용할 수 있는 정보들도 각각의 컴포넌트에 따로따로 전달하면서 전체적인 흐름이 난잡해졌습니다.
그래서 전반적으로 state 관리와 데이터 전달 구조를 개선하고자 리팩토링을 진행했습니다.

새로운 가위바위보 게임

다른분의 코드를 보고 아 이렇게도 할 수 있구나 하고 또한 공통 상수 및 리소스 분리를 하는게 좋다는 걸 알고 잇었는데 막상 사용하지 않았다 꺠닫게 되며 다시 짜보았습니다.

https://new-rsp-game.vercel.app/

공통 상수 및 리소스 분리

choices, choiceImgs, RESULT와 같은 상수와 이미지 리소스를 별도의 파일로 분리함으로써

  • 컴포넌트 간 중복 제거
  • 변경 시 유지보수 용이성 향상
  • 코드의 가독성 및 재사용성 증가
    라는 이점을 얻을 수 있도록 구조화했습니다.
import paperImg from "../assets/paper.png";
import rockImg from "../assets/rock.png";
import scissorsImg from "../assets/scissors.png";
import questionImg from "../assets/questionmark.png"; // <- 물음표 이미지 (customMarkImg)

const choices = {
  SCISSORS: "가위",
  ROCK: "바위",
  PAPER: "보",
};

const choiceImgs = {  
  가위: scissorsImg,
  바위: rockImg,: paperImg,
  처음: questionImg, 
};

const RESULT = {
  WIN: "이겼다",
  LOSE: "졌다",
  DRAW: "비겼다",
};

export { choiceImgs, choices, RESULT };

모듈 CSS 왜 사용하나

  • 컴포넌트 단위 스타일링이 가능해져 구조적인 코드 작성이 가능함.
  • 팀 프로젝트에서도 클래스명 충돌을 예방할 수 있음.
  • 스타일이 컴포넌트에 직접 연결되어 명확한 책임 분리가 가능함.

UI로직

전체 레이아웃은 header, main, footer 세 영역으로 구성하였습니다.

header와 footer는 text-align: center를 적용하여 텍스트가 가운데 정렬되도록 설정하였습니다.

main 영역에는 사용자 선택을 보여주는 Card 컴포넌트와, 여러 개의 버튼으로 구성된 ButtonList 컴포넌트를 배치하였습니다.

main에는 display: flex 속성을 적용하고 flex-direction: row로 설정하여, 카드와 버튼 리스트가 가로 방향으로 나열되도록 하였습니다.

ButtonList 역시 display: flex를 적용하고 flex-direction: column으로 설정하여, 내부 버튼들이 세로 방향으로 정렬되도록 구성하였습니다.

전체적인 흐름

전체적인 UI를 짠 후 먼저 handleUserChoice함수를 통해 게임이 진행이 되는데 일단 Object.values를 이용해 만든 버튼들에서 choice값을 받아와 choice된 값을

 {Object.values(choices).map((choice)=>(
          <Button choice={choice} disabled ={isPlaying} 
onclick={()=> handleUserChoice(choice)} />
     ))}

setUserChoice에 넣어준다 그 후 RandomSelect()함수에서 랜덤으로 하나를 고르게 해준다

 const RandomSelect = () => {
    const index =  Math.floor(Math.random()*3);
    return Object.values(choices)[index];
  }

choices의 values의 index를 받아오게 해준다 그걸 setComputerChoice 넣어 determineWinner 함수에서 누가 이기는지 비교를 한다.

 const determineWinner = (user,computer) => {
    if(user===computer) {
      return [RESULT.DRAW,RESULT.DRAW];
    }
    if((user===choices.SCISSORS && computer === choices.PAPER) 
      || (user===choices.ROCK && computer === choices.SCISSORS) 
    || (user===choices.PAPER && computer === choices.ROCK) ) {
      return [RESULT.WIN,RESULT.LOSE];
    }
    else {
      return[RESULT.LOSE,RESULT.WIN];
    }
  }

handleUserChoice 는 이렇게 작동한다.
isPlaying 이라는 상태를 만들어 플레이중이면 return 하고 아니면 true 로만들어 준다 그리고 유저가 선택을 한 후 0.5초 뒤에 컴퓨터가 선택을 하게된다. 그 후 result값을 넣어줘 각 card에 반환시켜준다.

const handleUserChoice = (choice) => {
    if (isPlaying) return;
    setIsPlying(true);
    setUserChoice(choice);
    setTimeout(() => {
      const comChoice = RandomSelect();
      setComputerChoice(comChoice);
      setResult(determineWinner(choice,comChoice));
      setIsPlying(false);
    }, 500);
    
  }
profile
나만의 기록장

0개의 댓글