이 블로그를 참고해 토이프로젝트를 진행했습니다.
nxn 형식으로 된 카드들에 적힌 글자 중 다른 한가지 카드를 찾아내는 게임
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를 사용하기 위해서 설치를 해주어야 하는데,
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"}/>
이렇게 원하는 상태에 대한 값을 전달해 줄 수 있다.
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를 입력해주면 끝~
이 블로그의 설명을 따라서 실행하였다.
구글 폰트를 활용해 링크로 불러온 웹폰트를 스타일 컴포넌트에 바로 적용하면 폰트가 바로 적용되는게 아니라, 기본 폰트가 보여졌다가 원하는 폰트로 변경되는 문제가 발생했다. 이렇게 작동하는 이유는 리액트가 렌더링 될 때마다 폰트도 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";
`;
이렇게 하나의 토이플로젝트를 또 만들었다. 리액트로 만드는 두번째 프로젝트이자 스타일드 컴포넌트를 처음 사용해보았는데, 확실히 직접 사용을 해봐야 어떻게 사용하는지 더 잘 알 수 있는 것 같다. 어떻게 공부해야하는지 감도 좀 잡은 것 같고.. 전역으로 사용하는 방법이랑 어떻게 모듈화하면 좋을지 좀 더 고민해봐야겠다.
전체적인 디자인 변경 및 정답 시 alert대신 modal창으로 변경
만들어주신 게임 눈빠져가며 하는데 4번째로 넘어가기가 정말 어렵네요 ㅋㅋ 타이머까지 구현을 하셔서 훨씬 더 재밌고 쫄리는 마음으로 즐겼습니다. 디자인이 조금만 더 그럴싸하면 좋을 것 같아요! alert창 대신 modal 화면을 만들어 보는 것도 도움이 될거에요 ~ 잘 감상하고 갑니다!! 다음 프로젝트도 기대할게요 :)