[프로젝트 회고] 온라인 야구 게임 (React)

Fact(사실)

프로젝트 개요

기간

  • 5월 3일 ~ 5월 14일 (2주)

개발 환경

  • React
  • Spring
  • mysql
  • AWS

GitHub

Baseball Game Repository 바로가기

구현 내용

  • 현재 투수, 타자 정보와 Strike, Ball, Out, Hit에 결과에 따라 Score와 Game Hitory 값을 저장해서 보여주는 온라인 야구 게임 구현

프로젝트 목표

이번 프로젝트를에서 우리가 내세운 목표는 각자 지난 리액트 프로젝트에서 경험하지 못한 기술, 방식으로 기능을 구현하며 팀원 모두가 성장할 수 있는 방향으로 진행해보는 것이었다. 그래서 아직 Reducer, Context API 활용이 낯설었던 나는 Pitch 마다 변화하는 상태를 중점적으로 관리해야하는 컴포넌트의 구현을 맡았고, React내에서 Canvas 적용하는 법을 배우기 위해 다이아몬드 모양의 야구 경기장을 Canvas로 구현하였다. Styled-Component로 애니메이션을 적용하는 법을 제대로 익히고자 player가 경기장을 따라 base로 이동하여 해당 base에 서있는 애니메이션을 구현하였다. 또한 우리 팀은 구성원 모두 반응형 UI 생성을 원했기 때문에 반응형 컴포넌트를 별도로 생성하여 반응형 Layout 생성에 활용하였다.

✅ Styled-Component로 반응형, 애니메이션 구현하기
✅ Reducer, Context API로 상태 관리하기

반응형 컴포넌트로 반응형 UI 구현하기


반응형 레이아웃을 사용하는 스타일 컴포넌트에 적용할 반응형 컴포넌트 Responive.jsx를 생성하였다. 프로젝트 내에서 반응형을 적용시킬 width에 따른 속성 값을 지정해주고, 반응형 레이아웃을 쓸 컴포넌트에 Responsive 컴포넌트를 상속하여 재사용하는 방식으로 반응형 컴포넌트를 구현하도록 했다.

import styled from "styled-components";

const Responsive = ({ children, ...rest }) => {
  // style, className, onClick, onMouseMove 등의 props를 사용할 수 있도록
  // …rest를 사용하여 ResponsiveBlock에게 전달하도록 함
  return <ResponsiveBlock {...rest}>{children}</ResponsiveBlock>;
};

const ResponsiveBlock = styled.div`
  max-width: 1440px;
  height: 100vh;
  padding: 0 80px;
  margin: 0 auto; /* 중앙 정렬 */
  box-sizing: border-box

  /* 브라우저 크기에 따라 가로 크기 변경 */
  @media (max-width: 1024px) {
    // 브라우저가 1024미만일때 안에 내용물이 768
    width: 768px;
    padding: 0 60px;
  }
  @media (max-width: 768px) {
    // 브라우저가 768 미만일때 안에 내용물이 332
    width: 100%;
    padding: 0 50px;
  }
`;

export default Responsive;

React에서 Canvas 사용하기

처음에는 React에서 어떤 방법으로 캔버스를 생성해서 어느 시점에 캔버스를 띄우면 될지 혼동되었다. 그런데 생각해보니 캔버스 적용은 DOM이 만들어 진 후 하면 될 일.. 그래서 useRef로 DOM에서 canvas 태그를 선택한 후 useEffect에서 getContext를 해주고, 다이아몬드 모양으로 line을 그려주었다.

const PlayFieldCanvas = () => {
  const canvasRef = useRef();

  useEffect(() => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext("2d");
    canvas.style.position = "absolute";
    canvas.style.width = "100%";
    canvas.style.height = "100%";
    canvas.width = canvas.offsetWidth;
    canvas.height = canvas.offsetHeight;
    drawField(ctx, canvas.offsetWidth, canvas.offsetHeight);
  }, []);

  return (
    <div>
      <canvas ref={canvasRef} />
    </div>
  );
};

useReducer, Context API로 복잡한 상태 관리하기

야구 게임은 Strike, Ball, Out 결과에 따라 득점을 할 수도 공수 교대가 일어날 수도 있는 복잡한 상태 구조를 가진다. 이러한 상태를 각각 관리하게 될 경우 context로 전달하기도, state 변화를 파악하기도 까다롭다는 문제가 있었다. 그래서 useReducer를 사용해서 Pitch 결과에 따른 상태를 모아서 관리하고 미리 정의한 action에 맞게 상태를 업데이트하는 방식으로 Strike, Ball, Out, Hit 상태를 다루었다.

const initialSBOState = {
  strike: 0,
  ball: 0,
  out: 0,
};

const SBOReducer = (state, action) => {
  switch (action.type) {
    case "STRIKE":
      return { ...state, strike: state.strike + 1 };
    case "BALL":
      return { ...state, ball: state.ball + 1 };
    case "OUT":
      return { ...state, out: state.out + 1 };
    case "HIT":
      return { ...state, strike: 0, ball: 0 };
    case "SB_RESET":
      return { ...state, strike: 0, ball: 0 };
    case "TOTAL_RESET":
      return { ...initialSBOState };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};

Feelings(느낌)

잘한 점

  • 처음으로 페어와 함께 부족한 부분에 대해 스터디를 진행하며 채워나가는 시간을 가졌다. 이번에는 반응형 UI 구현 방법에 대해 공부하는 시간을 가졌는데, FE를 공부하는 사람으로써.. 언젠간 해야하는 반응형 학습을 할 수 있어 유익한 시간이었다. 오랜 숙원 사업을 끝낸 기분!
  • 익숙하지 않은 방법으로 구현하고자 하였다. 기존에는 미션 수행에 초점이 맞춰 있었던 터라 지난 프로젝트에서 했던 방식대로 설계하고 구현을 진행할 뿐 어떤 구조로 컴포넌트를 설계해야 보다 효율적으로 렌더링할 수 있을지에 대해 생각하지 못하였다. 이번 프로젝트에서는 감사하게도 리액트 관련 지식과 경험이 풍부한 팀원과 함께하며 Context API를 사용할 때 render가 필요한 부분만 render하는 방법, 고차 컴포넌트(Higher-order Component) 등의 새로운 개념을 학습할 수 있었다.
  • 모르거나 헷갈리는 내용을 꼭 이해하고 넘어가도록 노력했다. 처음에는 '내 질문이 다른 팀원의 시간을 뺏으면 어떡하지?'라는 걱정이 들었다. 하지만 모르는 걸 그냥 넘기지 않고 이해하고 넘어가니 기능이나 팀원의 의견을 이해하지 못해 발생할 수 있는 의사소통 문제를 방지할 수 있었다.

아쉬운 점

  • 설계에 집중한 결과 기능 구현을 할 시간이 부족하였다. 분명 기능을 구현하는 과정에서 최적화를 위해 여러 방법으로 코드를 수정해보며 배우는 바가 많았을 것이다. 구현보다 성장에 초점을 맞추자는 초반의 다짐과는 달리 데드 라인이 다가오자 정말 기계적으로 코드를 짜는 내 자신을 발견하였다.. 구현 못했다고 누가 뭐라하는 것도 아닌데 말이다..🤦‍♀️ 그 당시에는 열심히 작업해준 백엔드 팀원과 유용한 기능을 구현해주고 새로운 지식을 익힐 수 있게 배려해준 팀원에게 그래도 App이 기본적인 기능은 하는 모습을 보여주고 싶었기에 초반에 정했던 목표를 지키지 못햇던 것이 아쉽다.
  • 백엔드와 소통이 부족했던 점. 이 부분은 윗 내용과 맥락이 이어지는 내용인데 만일 설계와 구현이 같이 진행되어서 API를 빨리 사용해볼 수 있었더라면, API 개선점을 피드백하며 백엔드와 소통할 수 있는 시간이 많았을텐데라는 생각이 들었다.

Findings(교훈)

  • 처음 설계로 끝까지 구현하기는 어렵다. 설계와 구현을 병행하며 개선해 나가자.
  • API 구조 설계는 어렵고, 힘들다. 그러니 완성되기 전까지 최대한 많은 의견을 공유하며 여러 경우의 수를 생각해야 나중에 백엔드, 프론트 모두 편하게 작업할 수 있다.
  • 새로운 개념을 적용하기 전엔 꼭 개인적으로 학습하고 우리 프로젝트에 어떤 방식으로 적용할 수 있을지 생각해보는 시간을 가지자. 팀 미팅때 제시할 수 있는 의견의 퀄리티가 달라진다.
  • 모르는 내용은 꼭 알고 넘어가자. 나중에 이해되겠지~ 나중에 물어보면 되겠지~하면 이미 프로젝트 막바지일 것이다.
  • 회의록의 중요성!! 그날 나눴던 이야기를 꼭 문서화하자.. 코드 수정이 반복되면 어제 한 회의 내용도 헷갈린다.
  • PR은 자주 자주하자. 니 코드 = 내 코드가 될 수있도록 팀원 간 코드리뷰 시간을 가지자. 서로의 코드를 보며 의문이 드는 사항을 수정해나가다 보면 더 좋은 구조로 리팩토링할 수 있을 것이다.

Future(앞으로)

  • 팀원에게 긍정적인 영향을 줄 수 있도록 다양한 관점에서 유익한 피드백을 주자.
  • 내가 공부한 지식이 팀원에게 도움이 될 수 있으니 프로젝트 중 알게된 내용을 정리해서 공유하는 시간을 가지자.
  • 매일 회고를 통해 부족한 부분을 채워나갈 수 있도록 하자.
  • 지식을 확장해나가자. 특히 리액트 성능을 최적화해 나갈 수 있는 여러 방법들을 찾아 적용해보자.

좋은 웹페이지 즐겨찾기