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 };
전체 레이아웃은 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);
}