이번 프로젝트를에서 우리가 내세운 목표는 각자 지난 리액트 프로젝트에서 경험하지 못한 기술, 방식으로 기능을 구현하며 팀원 모두가 성장할 수 있는 방향으로 진행해보는 것이었다. 그래서 아직 Reducer, Context API 활용이 낯설었던 나는 Pitch 마다 변화하는 상태를 중점적으로 관리해야하는 컴포넌트의 구현을 맡았고, React내에서 Canvas 적용하는 법을 배우기 위해 다이아몬드 모양의 야구 경기장을 Canvas로 구현하였다. Styled-Component로 애니메이션을 적용하는 법을 제대로 익히고자 player가 경기장을 따라 base로 이동하여 해당 base에 서있는 애니메이션을 구현하였다. 또한 우리 팀은 구성원 모두 반응형 UI 생성을 원했기 때문에 반응형 컴포넌트를 별도로 생성하여 반응형 Layout 생성에 활용하였다.
✅ Styled-Component로 반응형, 애니메이션 구현하기
✅ Reducer, Context API로 상태 관리하기
반응형 레이아웃을 사용하는 스타일 컴포넌트에 적용할 반응형 컴포넌트 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에서 어떤 방법으로 캔버스를 생성해서 어느 시점에 캔버스를 띄우면 될지 혼동되었다. 그런데 생각해보니 캔버스 적용은 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>
);
};
야구 게임은 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}`);
}
};