다른 글자 찾기

dana·2021년 12월 17일
8

토이프로젝트

목록 보기
6/17
post-thumbnail

이 블로그를 참고해 토이프로젝트를 진행했습니다.

다른 글자 찾기 게임

게임 설명

nxn 형식으로 된 카드들에 적힌 글자 중 다른 한가지 카드를 찾아내는 게임

기능구현

  • 시작페이지
    - 시작 버튼 클릭시 게임 화면으로 전환
  • 게임 페이지
    - 랜덤으로 다른 글자 배치
    - 다른 글자 클릭 시, 성공이면 다음 단계로 진입
    - 실패하거나 제한 시간 경과 시, 게임 종료
    - 타이머 구현

실행 화면

게임 플레이

게임 시작화면

게임 플레이

시간 초과시

오답 클릭시

레벨 10 이상시

실행 방법

개발 환경

  • Mac OS 사용
  • VSC Version: 1.62.3
  • yarn 1.22.17
  • react 17.0.2

사용 기술

HTML / CSS / JavaScript / React / styled-components

개발하면서 느낀 점

오류 사항

실패화면 띄운 후, 시간이 멈추지 않는 문제

처음에 화면이 뜨자마자 타이머가 돌아가도록 만들기 위해, useEffect를 처음 사용해보았다. 처음 작성했던 코드는

  useEffect(() => {
    const timer = setTimeout(() => {
      setTime((prev) => (prev - 0.01).toFixed(2));
    }, 10)

    if (time <= 0) {
      clearTimeout(startTimer);
    }

    return () => clearTimeout(startTimer);
  });

이렇게 useEffect안에 timer라는 이름의 setTimeout 함수를 생성해 주었다.
이렇게 작성된 상태에서 코드의 효율성을 위해 오답 클릭 시 실행되는 clickWrong이라는 함수를 활용해 시간이 초과되는 경우에도 같은 함수가 실행되도록 만들었다. 그러기 위해서 useEffect안에서 선언된 timer 함수를 밖으로 빼는 작업이 필요했고 다음과 같은 코드를 작성했다.

let startTimer;

  const timer = () =>
    (startTimer = setTimeout(() => {
      setTime((prev) => (prev - 0.01).toFixed(2));
    }, 10));

  useEffect(() => {
    timer();

    if (time <= 0) {
      clickWrong();
    }

    return () => clearTimeout(startTimer);
  });

  const clickWrong = () => {
    clearTimeout(startTimer);
    setStop(false);
  };

근데 이렇게 만들었더니 실패화면에서는 시간이 멈추지 않는 문제가 발생했다.

눙물,,, 그래서 체크해봤더니 clickWrong 함수 자체의 문제는 아닌거 같고 (-> 왜냐면 같은 함수인데 시간이 0초일 때는 잘 실행되는 반면, 실패 화면에서만 시간이 안멈췄기 때문) 자식 컴포넌트로 내려갈 때 clearTimeout이 전달이 안되나 싶었는데 그 문제도 아닌 것 같았다.
그래서 조은님한테 조언을 구했더니, useEffect의 두번째 인자에 [time] array를 넣어주는 것으로 해결되었다.

let startTimer;

  const timer = () =>
    (startTimer = setTimeout(() => {
      setTime((prev) => (prev - 0.01).toFixed(2));
    }, 10));

  useEffect(() => {
    timer();

    if (time <= 0) {
      clickWrong();
    }

    return () => clearTimeout(startTimer);
  }, [time]);

그래서 이 문제가 왜 일어났느냐
useEffect이 실행되는 조건은 마운트, 언마운트, 업데이트될 때 이렇게 세가지 조건에서 실행된다.

여기서 문제가 발생한 부분은 업데이트될 때인데 setTimeout을 멈춘 뒤에 setStop으로 useState가 변경되면서 재렌더링이 일어난다. 이 때 재렌더링 과정에서 useEffect가 재실행되어 timer 함수가 실행되게 되고, 멈췄던 타이머가 다시 실행되면서 타이머가 멈추지 않고 돌아가는 것처럼 보이게 된다.

그래서 해결 방안으로 useEffect의 두번째 인자에 array 형태로 time state를 넣어주었다. 인자값이 없을 때는 재렌더링될 때마다 useEffect가 실행되는 반면, useEffect에 두번째 인자값(의존성배열)으로 넣어주는 state 값이 변경될 때만 useEffect가 실행된다. 그래서 time이 변경되지 않으면 -> setTimeout이 멈추면 useEffect가 실행되지 않아 시간이 그대로 멈추게 된다.

배운 점

styled component를 사용해보았다!

다음주 스터디부터 styled component를 사용하게 되어, 먼저 사용해보았다.
styled component를 사용하기 위해서 설치를 해주어야 하는데,
yarn을 사용하고 있어 yarn add styled-components로 설치를 해주었다.

styled component는 정말 말 그대로 스타일된 컴포넌트를 생성해주는 기술이었다.
예를 들어 Main이라는 태그 이름을 가진 section html 태그에 스타일을 입혀 사용하고 싶다면

const Main = styled.section`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  background-color: black;
  color: white;
`;

이렇게 정의해주면 jsx에서 <Main> minju velog </Main> 이렇게 사용 가능하다.
만약 여기서 리액트 컴포넌트에서 사용됐던 것 같이 props 값을 전달하고 싶다면,

const Stop = styled.div`
  display: ${(props) => props.display};
  position: absolute;
  left: 0;
  top: 0;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  background-color: rgba(255, 255, 255, 0.8);
  text-align: center;
  z-index: 100;
`;

<Stop display = {"none"}/>

이렇게 원하는 상태에 대한 값을 전달해 줄 수 있다.

styled-components에서 animation 사용하기

css에서 사용되던 animation을 사용하기 위해 keyframe을 만들어 입력하고 싶었는데, 어떻게 해야할 지 고민하다 검색해봤더니 keyframe도 컴포넌트로 만들어 사용해야한다는 글을 보았다.

import styled, { keyframes } from "styled-components";
keyframe을 import해주고,

const clickMe = keyframes`
  from {
    box-shadow: 0 0 0 0 rgba(0, 172, 193, 0);
  }
  50% {
    box-shadow: 0 0 0 5px rgba(0, 172, 193, 0.5);
  }
  to {
    box-shadow: 0 0 0 0 rgba(0, 172, 193, 0);
  }
`;

keyframe을 먼저 선언해준다. 여기서 애니메이션이 사용되는 곳보다 먼저 선언해야 컴포넌트 애니메이션에서 keyframe을 불러올 때 선언된 keyframe을 인식할 수 있다.

const Restart = styled.button`
  width: 100px;
  height: 50px;
  font-size: 20px;
  background-color: #00acc1;
  border: none;
  border-radius: 8px;
  font-family: inherit;
  cursor: pointer;
  z-index: 110;
  animation: ${clickMe} 1s linear infinite;
`;

이렇게 clickMe를 입력해주면 끝~

styled-components에서 웹폰트 사용하기

이 블로그의 설명을 따라서 실행하였다.

구글 폰트를 활용해 링크로 불러온 웹폰트를 스타일 컴포넌트에 바로 적용하면 폰트가 바로 적용되는게 아니라, 기본 폰트가 보여졌다가 원하는 폰트로 변경되는 문제가 발생했다. 이렇게 작동하는 이유는 리액트가 렌더링 될 때마다 폰트도 reload되기 때문이라, 이 부분을 해결할 수 있는 방법으로 폰트를 불러오는 파일을 따로 만든 뒤, 파일을 import해서 사용해야한다.

1. font.css파일 생성하기

font.css파일에 @font-face를 작성해준다.

@font-face {
  font-family: "SANJUGotgam";
  src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2112@1.0/SANJUGotgam.woff")
    format("woff");
  font-weight: normal;
  font-style: normal;
}

2. createGlobalStyle, font.css import

스타일드 컴포넌트에 있는 createGlobalStyle 를 불러온다.
불러온 createGlobalStyle을 이용해서 폰트 디자인을 선언해준다.

const GlobalStyle = createGlobalStyle`
font-family : "SANJUGotgam"`;

3. 컴포넌트를 JSX에 적용

<Main>
	<GlobalStyle />
	<Title>다른 글자찾기 게임을 시작해봅시다!</Title>
	<StartBtn onClick={gameStart}>START!</StartBtn>
</Main>

4. 스타일드 컴포넌트에 폰트 스타일 적용

const Title = styled.h2`
  font-size: 42px;
  font-family: "SANJUGotgam";
`;

후기

이렇게 하나의 토이플로젝트를 또 만들었다. 리액트로 만드는 두번째 프로젝트이자 스타일드 컴포넌트를 처음 사용해보았는데, 확실히 직접 사용을 해봐야 어떻게 사용하는지 더 잘 알 수 있는 것 같다. 어떻게 공부해야하는지 감도 좀 잡은 것 같고.. 전역으로 사용하는 방법이랑 어떻게 모듈화하면 좋을지 좀 더 고민해봐야겠다.

20211222

전체적인 디자인 변경 및 정답 시 alert대신 modal창으로 변경

profile
PRE-FE에서 PRO-FE로🚀🪐!

6개의 댓글

comment-user-thumbnail
2021년 12월 19일

만들어주신 게임 눈빠져가며 하는데 4번째로 넘어가기가 정말 어렵네요 ㅋㅋ 타이머까지 구현을 하셔서 훨씬 더 재밌고 쫄리는 마음으로 즐겼습니다. 디자인이 조금만 더 그럴싸하면 좋을 것 같아요! alert창 대신 modal 화면을 만들어 보는 것도 도움이 될거에요 ~ 잘 감상하고 갑니다!! 다음 프로젝트도 기대할게요 :)

1개의 답글
comment-user-thumbnail
2021년 12월 23일

50탄까지 했는데, 몇탄이 마지막이져 ㅎㅎ
ctrl + f 누르면 새로고침돼서 start화면으로 보내버리는거 어떠신가여

1개의 답글
comment-user-thumbnail
2021년 12월 23일

와!! 디자인도 업데이트가 되었네요! +_+bbb 글자도 매번 달라지는 건 어떤가요?

1개의 답글