리액트로 간단한 웹 게임(구구단) 만들기
제로초님의 강의를 참고하여 이해한 내용을 바탕으로
스스로 다시 만들어보았다.
함수 컴포넌트와 클래스형 컴포넌트 2가지로 동일한 구구단 게임을 만들어 볼 것이다.
완성한 구구단 웹게임
작성한 전체 jsx 코드는 다음과 같다.
import React, { useState, useRef } from 'react';
import './gugudan.css';
function GuGuDanGame() {
const [firstNumber, setFirstNumber] = useState(randomNumber());
const [secondNumber, setSecondNumber] = useState(randomNumber());
const [result, setResult] = useState('');
const [value, setValue] = useState('');
const inputRef = useRef();
function randomNumber() {
return Math.ceil(Math.random() * 9);
}
const onSubmit = (e) => {
e.preventDefault();
if (parseInt(value) === firstNumber * secondNumber) {
setResult('정답입니다.');
setValue('');
newQuiz();
inputRef.current.focus();
} else {
setResult('틀렸어요!');
setValue('');
inputRef.current.focus();
}
};
const userInput = (e) => {
setValue(e.target.value);
};
const newQuiz = () => {
setFirstNumber(randomNumber());
setSecondNumber(randomNumber());
};
return (
<div className="gugudan-container">
<form onSubmit={onSubmit}>
<h2 className="title">구구단 게임</h2>
<p className="quiz-txt">
문제 : {firstNumber} × {secondNumber}
</p>
<input ref={inputRef} value={value} onChange={userInput} placeholder="정답을 입력하세요." type="number" />
<button>확인</button>
<p className="result"> {result}</p>
</form>
</div>
);
}
export default GuGuDanGame;
작성한 코드를 하나씩 살펴보며 어떤 코드인지 설명과 함께 복습해보고자 한다.
import React, { useState, useRef } from 'react';
React와 react hooks를 import하는 코드이다.
구구단 만들기에서 사용한 hooks는 useState와 useRef 두가지를 사용하였다.
useRef
는 특정 DOM을 선택
해야 할 때 사용한다.
예를 들면 자바스크립트의 getElementById, querySelector와 같다.
여기서는 input 박스에 foucs 되도록 하기 위해 사용했다.
const [firstNumber, setFirstNumber] = useState(randomNumber());
const [secondNumber, setSecondNumber] = useState(randomNumber());
const [result, setResult] = useState('');
const [value, setValue] = useState('');
const inputRef = useRef();
다음은 state 변수들
이다.
firstNumber
와 secondNumber
구구단의 첫번째 숫자와 두번째 숫자는 1~9의 랜덤한 수를 초기값으로 설정하도록 randomNumber 라는 함수를 만들고 반환 값을 초기값으로 사용할 수 있도록 작성했다.
result
정답이면 "정답입니다." 오답이면 "틀렸어요!"를 보여줄 때 사용할 state 변수이다.
value
사용자가 입력한 input의 value를 받을 state 변수이다.
inputRef
input 태그에 foucs 함수를 사용할 것이기 때문에 DOM을 선택해야 하므로 useRef hook를 사용하였다.
function randomNumber() {
return Math.ceil(Math.random() * 9);
}
randomNumber 함수
앞에서 선언한 state 변수 firstNumber
와 secondNumber
의 초기값으로 들어갈 숫자를 위해 만든 함수이다.
호출시 return 값으로 1~9 사이의 난수를 반환한다.
Math.ceil
은 random 함수가 0~1 사이의 부동소수점을 반환하기 때문에 강제로 올림하여 1이상의 정수를 반환하도록 하였다.
const 변수로 익명함수를 만들어서 반환해도 되지만 function으로 만든 것은 함수를 state변수보다 아래쪽에 작성해서 사용하고 싶었기 때문에.. (호이스팅 이용하려고..!)
const onSubmit = (e) => {
e.preventDefault();
if (parseInt(value) === firstNumber * secondNumber) {
setResult('정답입니다.');
setValue('');
newQuiz();
inputRef.current.focus();
} else {
setResult('틀렸어요!');
setValue('');
inputRef.current.focus();
}
};
onSubmit 함수
form 태그가 전송됐을 때 발생하는 이벤트인데 기본 동작을 막기 위해 e.preventDefault();
를 사용했다.
그리고, 폼 전송이 된 시점은 사용자가 input 란에 빈값 또는 무언가 값을 입력했을 것이기 때문에 if-else
문을 사용하여 사용자가 입력한 input의 value와 문제의 firstNumber * secondNumber
의 값이 일치하는지 확인하도록 한다.
input에 작성한 value는 숫자가 아닌 문자열이기 때문에 parseInt
를 사용하여 숫자로 변환시켰다.
만약 firstNumber * secondNumber
와 parseInt(value)
의 값이 일치한다면 state 변수 result
를 "정답입니다."로 변경한다.
또, input 태그의 value를 깨끗히 지워주기 위해 setValue('');
를 사용했다.
정답을 맞혔기 때문에 newQuiz 함수를 호출
하여 새로운 문제를 보여주도록 하였다.
inputRef.current.focus();
는 이전에 useRef로 찾은 input 태그에 focus 되도록 하는 코드이다.
만약, 유저가 input에 입력한 value와 문제의 정답이 일치하지 않으면 틀렸어요! 메시지가 출력되며 새로운 문제는 생성하지 않고 작성한 정답 부분만 지워서 다시 풀 수 있도록 하는 코드이다.
const userInput = (e) => {
setValue(e.target.value);
};
input 태그에 사용자가 value값으로 무언가 입력했을 때 state변수가 감지하여 변경할 수 있도록 하는 코드이다.
const newQuiz = () => {
setFirstNumber(randomNumber());
setSecondNumber(randomNumber());
};
앞서 onSubmit 함수에서 이 함수를 호출하도록 작성했었다.
newQuiz 함수는 유저가 정답을 맞혔을 때 사용되는 함수이다.
newQuiz
함수가 호출되면 새로운 문제를 만들도록 state 변수 firstNumber와 secondNumber의 값을 다시 랜덤한 난수(1~9)를 생성하는 함수를 호출하여 반환 받은 값으로 변경하도록 한다.
즉, 새로운 구구단 문제를 만드는 함수인 것이다.
return (
<div className="gugudan-container">
<form onSubmit={onSubmit}>
<h2 className="title">구구단 게임</h2>
<p className="quiz-txt">
문제 : {firstNumber} × {secondNumber}
</p>
<input ref={inputRef} value={value} onChange={userInput} placeholder="정답을 입력하세요." type="number" />
<button>확인</button>
<p className="result"> {result}</p>
</form>
</div>
);
컴포넌트 return에 작성한 부분은 앞에서 이미 설명했던 내용들이라 생략한다.
useRef 사용한 부분은 사용자가 문제의 정답을 입력할 때 확인버튼 대신 키보드 엔터를 치면 없어도 되지만,
확인 버튼을 누르는 경우에는 foucs가 잡히지 않기 때문에 useRef로 input 태그에 focus 되도록 해야 한다.
함수 컴포넌트로 만들었던 구구단을 클래스형 컴포넌트로 코드를 수정하였다.
import React from 'react';
import './gugudan.css';
class GuGuDanGame2 extends React.Component {
constructor(props) {
super(props);
this.state = {
firstNumber: this.randomNumber(),
secondNumber: this.randomNumber(),
result: '',
value: '',
};
}
inputTag = (inputTag) => {
this.input = inputTag;
};
randomNumber = () => {
return Math.ceil(Math.random() * 9);
};
onSubmit = (e) => {
e.preventDefault();
if (parseInt(this.state.value) === this.state.firstNumber * this.state.secondNumber) {
this.setState({
result: '정답입니다.',
value: '',
});
this.newQuiz();
} else {
this.setState({
result: '틀렸어요!',
value: '',
});
}
this.input.focus();
};
userInput = (e) => {
this.setState({
value: e.target.value,
});
};
newQuiz = () => {
this.setState({
firstNumber: this.randomNumber(),
secondNumber: this.randomNumber(),
});
};
render() {
return (
<div className="gugudan-container" style={{ background: 'steelblue' }}>
<form onSubmit={this.onSubmit}>
<h2 className="title">구구단 게임</h2>
<p className="quiz-txt">
문제 : {this.state.firstNumber} × {this.state.secondNumber}
</p>
<input ref={this.inputTag} value={this.state.value} onChange={this.userInput} placeholder="정답을 입력하세요." type="number" />
<button>확인</button>
<p className="result"> {this.state.result}</p>
</form>
</div>
);
}
}
export default GuGuDanGame2;
render함수는 state값이 변경될 때마다 계속 호출된다고 해서 인라인으로 작성하지 않고 모두 바깥으로 빼서 메소드 형식으로 작성해주었다.
함수 컴포넌트 구구단과 구분하기 위해 배경색만 살짝 다르게 하고 나머지 동작은 모두 동일하다.
다른 점이라하면 역시 hooks 부분이 가장 컸다.
useState 라던가, useRef 부분은 완전히 사용 방법이 다르기 때문이다.
확실히 처음에 함수 컴포넌트로 만들고 클래스형 컴포넌트로 변경하려니 귀찮은 this를 계속 사용하는 것이 단점으로 느껴졌다.
그래도 class로 작성하는 방법도 알아야 16.8버전 이전에 클래스 컴포넌트로 작성된 코드를 읽을 수 있기 때문에 클래스 컴포넌트로도 변경해보았다.