틱택토 게임은 두 명이 번갈아가며 O와 X를 3×3 판에 써서 같은 글자를 가로, 세로,
혹은 대각선 상에 놓이도록 하는 놀이이다.
이 게임을 구현하기위한 단계를 하나하나 작성해서 써볼 예정
컴포넌트
Game : Board, Square컴포넌트의 부모컴포넌트
Board : Square컴포넌트에서 버튼엘리먼트를 만들었다면 9개의 버튼을 만들어주고 이벤트를 주기 위한 컴포넌트
Square : 버튼 엘리먼트를 만드는 컴포넌트
class Square extends React.Component{
render(){
return(
<button>
버튼
</button>
)
}
}
class Board extends React.Component{
render(){
return(
<div></div>
)
}
}
class Game extends React.Component{
render(){
return(
<div>
<Square />
</div>
)
}
}
Game 컴포넌트에 Square 컴포넌트에 button을 가져왔다.
이 버튼을 9개를 만들어주기위해
Square 컴포넌트에 버튼엘리먼트를 만들고 만든 버튼을 Board에 <div></div> 3개씩 만들어 3x3을 구현했다.
Board에서 만든 button 엘리먼트들을 Game 컴포넌트에 불러왔다
class Square extends React.Component{
render(){
return(
<button>
버튼
</button>
)
}
}
class Board extends React.Component{
render(){
return(
<div>
<div></div>
<div>
<Square />
<Square />
<Square />
</div>
<div>
<Square />
<Square />
<Square />
</div>
<div>
<Square />
<Square />
<Square />
</div>
</div>
)
}
}
class Game extends React.Component{
render(){
return(
<div>
<Board />
</div>
)
}
}
결과
해석
Game 컴포넌트에서 Board컴포넌트를 가져오고 Board컴포넌트에서 Square 컴포넌트를 가져오는 식으로 썼으며
이렇게 만들경우 Square컴포넌트에 버튼엘리먼트를 Board에 가져와
Board에 가져온 버튼을 Game로 브라우저에 랜더시키는 방식이다.
그후
ReactDOM.render(
<Game/>,document.querySelector('#root')
)
ReactDOM.render을 통해 브라우저에 랜더를 위해 작성해야하며 <Game/>컴포넌트를 불러와
document.querySelector를 이용해 <div id="root"> 를 '#root'로 내용물들을 보내주며 브라우저에 랜더시키는 형식이다.
Square 컴포넌트에 button 엘리먼트를 생성하고
그 버튼엘리먼트를 board에 각각 3개씩 3줄로 총 9개를 만들었다.
위에 처럼 컴포넌트를 불러오거나 props를 쓰기에는 너무 많이 작성해야하기때문에
class안에변수를 만들어 renderSquare함수를 따로 뺴서 <Square /> 를 넣어줫다
renderSquare = () => {
return <Square />
}
해석
Square컴포넌트를 직접호출을 하는것보다 함수를 이용해 호출해주는것이 '효율적'이라
renderSquare 함수에 리턴값을 Square Component 에 내용인 button을 반복해 9개의 {this.renderSquare()}를 작성해 줬다.
왜 효율적인가
<Square/> 를 9개를 작성하고 <Square value={this.props.value}}해줄때 넘길게 너무 많아서 하나하나 작성할때 오래걸릴뿐 아니라 props로 가져오기때문에 너무 길게 복잡하게 작성할수있기때문이다.
그후
<div>
<Square />
<Square />
<Square />
</div>
에서
<div>
{this.renderSquare()}
{this.renderSquare()}
{this.renderSquare()}
</div>
로 변경해줫다.
this.renderSquare는
renderSquare 를 this 가져오겠다. 하는뜻이고
renderSquare 함수를 작성해준다
renderSquare = () =>{
return <Square />
}
해석
renderSquare 라는 화살표함수를 ()=> {} 화살표 함수를
return을 통해 Square 컴포넌트를 넣어주며 버튼을 랜더시켜주었다.
그후
함수의 내용을 value로 채우기위해
<Square value={i} />
를 추가해주고
Square 컴포넌트에
class Square extends React.Component {
render() {
return (
<button>
{this.props.value}
</button>
);
}
}
버튼이라고 써있던곳에 {this.porps.value}를 넣어줫다
해석
Board Compenent
renderSquare = i => {
return <Square value={i} />
}
renderSquare함수선언 = (매개변수 i 값) =>{
return을 통해 (<Square 컴포넌트의 props로 value의 값을 = { i }로 넘겨주겠다. />
즉 Square 컴포넌트의 value를 props를 줘서
<button> {this.props.value} </button>
을 이용해 value값인 i를 props로 가져오겠다.
결과
이제 누르면 내용이 바뀌게 만들어야한다.
Board 컴포넌트에 state(상태)를 작성해주면된다.
state = {
squares:Array(9).fill(null),
}
버튼 클릭시 값이 바뀌게 Array 9 개 fill로 null값을 채웠다
Array(9).fill(null), 이 코드의 형태는
[null,null,null,null,null,null,null,null,null]로 나온다
state(상태) 에 squares라는 배열안에 null 이라는값을 채웠고 null의 값에 0~8이라는 9개의 버튼의 값을 채웟다.
이 null 값에 O와 X 를 채워야한다.
즉
<div>
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div>
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div>
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
이 인덱스 값에
0 1 2 3 4 5 6 7 8
[null,null,null,null,null,null,null,null,null]
을 채워 버튼에 넣을 값을을 배열에 담아두고 쓰기때문이다.
이 값을 채우기위해 이벤트를 넣어주고 값을 채워야한다.
handleClick = i =>{
console.log(this.state.squares)
}
renderSquare = i => {
return <Square onClick={this.handleClick(i)}
value={this.state.squares[i]} />
}
renderSquare의 매개변수인 I를 handleClick매개변수인 i 로 전달해야한다.
renderSquare 함수에 onClick={this.handleClick(i)} 를 넣어주면 handleClick을 불러올수있다.
handleClick의 역할은 클릭했을때에 이벤트를 걸어줄수있는 함수를 선언해주는 역할이다.
즉 클릭을 했을때 실행을 할수 있는 함수를 만든것이다.
하지만 랜더과정에서 바로 실행이되어 진행할수없어 onClick에 함수를 더 감싸줘야한다
왜? onClick={this.handleClick(i)에서 함수를 선언 한 상태이기 때문에
실행할수있게끔 해줘야한다
handleClick = i =>{
console.log(this.state.squares)
}
renderSquare = i => {
return <Square onClick={() => {this.handleClick(i)}
value={this.state.squares[i]} />
}
그후 onClick를 실행시키기위해
class Square extends React.Component {
render() {
return (
<button onClick={this.props.onClick}>
{this.props.value}
</button>
);
}
}
button 엘리먼트에 onClick 를 넣어준다
button을 눌렀을때 onClick 를 실행시켜
handleClick 함수를 실행시켜 squares배열의 null값을 X 로채워준다.
onClick={this.handleClick(i)}
button을 클릭했을때 onClick 이벤트를 실행시켜 X와 O를 나타내게 해야한다
state = {
// squares:[null,null,null,null,null,null,null,null,null]
squares:Array(9).fill(null),//[null,]
xIsNext: true,
}
handleClick = (i) => {
const squares = [...this.state.squares]
squares[i] = this.state.xIsNext ? 'X' : 'O'
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
})
xIsNext 는 boolean 불리언 값이므로 참과 거짓 을 가지고있는 데이터타입이니 X 와 O를
true일때 'X' false일때 O 값으로해준다.
Board 컴포넌트에 squares[i] = this.state.xIsNext ? 'X' : 'O'
state(상태)값의 xIsNext(boolean)의 value값이 true일때 'X' false일때 O 값으로
Square 컴포넌트의 button 에 렌더시킬 X 와 O를 번갈아가면서 나타내게 해준다
즉
if(xIsNext) { true 'X' } else { false 'O' } xIsNext ? 'X' : 'O'
squares의 value가 9개의 배열인 null값을 const squares = [...this.state.squares] Squares 컴포넌트안에 변수를 squares로 주고 배열인 state의 ...this로 state의 전체값을 불러와 squares값을 변수에 담는다. 담은 변수를 squares[i]9개의 배열을 클릭했을때 true일때 X false일때 O로 만들어줬다.
xIsNext의 값을 !(주로 false)로 주어 원래 X의 값을 만들어주는것을 O로 만들어주어 X와 O를 번갈아 가게 만들어주었다.xIsNext: !this.state.xIsNext,
한번이라도 승리하는 조건이 squares에 있으면 Board 컴포넌트에 handleClick 가 실행되지않도록 해주었다.
승자를 결정하기위해 만들어야할 코드
const calculateWinner = (squares) =>{
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (let i=0; i<lines.length;i++){
const [a,b,c] = lines[i]
if(squares[a] && squares[a] === squares[b] && squares[a] === squares[c]){
return squares[a]
}
}
return null
}
이 함수는 클릭할때 실행 시켜주는 함수이고 컴포넌트안에 들어가있지않다.
for (let i=0; i<lines.length; i++)
반복문에 i는 0부터 lines.length만큼 i++로 즉 8번 반복하는데
반복할때마다[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6],
만큼 반복한다.
const [a,b,c] = lines[i]
어떻게? lines[i]만큼 반복 [0,1,2] 즉 a:0 ,b:1 ,c:2
를 8번 반복하여 winner를 찾아내고 a,b,c는 squares[b]여도되고 [c]여도 어차피 내용은 똑같으니 결과는 같다.if(squares[a] && squares[a] === squares[b] && squares[a] === squares[c]){ return squares[a] }
&&을 통해 a,b,c가 모두 true인경우 게임을 끝낸다는뜻이다.
이 조건을 return을 통해 Winner를 정하거나 만약 승자가없을때return null
을 통해 승자가 없다는것을 작성했다.
그후 작성한 calculateWinner는 선언을 한 상태기 때문에 랜더가 되지 않으므로
calculateWinner 함수를 호출해야 한다 호출한 함수는
const winner = calculateWinner(squares)
calculateWinner함수를 가져와 winner 이라는 변수로 담아와
if( calculateWinner(squares) || squares[i]){
return
}
둘중 하나가 true인 경우 게임을 종료시키는 조건문을 작성했고
그후 승자와 패자의 표시 무승부 를 랜더시킬 코드를 작성해야한다
let status = winner
? `Winner: ${winner}` : `Next player : ${ this.state.xIsNext
? 'X' : 'O'}`
Next player 에 X 와 O 를나타내 순서를 만들어주고 winner에 X 와 O의 승자를 만들어준다.
그후 status라는 변수에 담아두어
<div className="status">{status}</div>
로 무승부와 승자 를 나타내고 다음 차례를 브라우저에 출력시킬수있게 작성했다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <!-- 객체 -->
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- 객체 -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<style>
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol, ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const calculateWinner = (squares) =>{
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (let i=0; i<lines.length;i++){
const [a,b,c] = lines[i]
if(squares[a] && squares[a] === squares[b] && squares[a] === squares[c]){
return squares[a]
}
}
return null
}
class Square extends React.Component{
render(){
return(
<button className="square"
onClick={ ()=>{ this.props.onClick()}}>
{this.props.value}
</button>
)
}
}
class Board extends React.Component{
state = {
squares:Array(9).fill(null),
xIsNext:true,
}
handleClick = (i) => {
const squares = [ ...this.state.squares]
if( calculateWinner(squares) || squares[i]){
return
}
squares[i] = this.state.xIsNext ? 'X' : 'O'
this.setState({
squares,
xIsNext:!this.state.xIsNext
})
}
renderSquare = (i) => {
return <Square onClick={ ()=> { this.handleClick(i)}}
value={this.state.squares[i]}/>
}
render (){
const { renderSquare,state:{squares}} = this
const winner = calculateWinner(squares)
let status = winner
? `Winner: ${winner}` : `Next player : ${ this.state.xIsNext
? 'X' : 'O'}`
return(
<div>
<div className="status">{status}</div>
<div className='board-row'>
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className='board-row'>
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className='board-row'>
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
)
}
}
class Game extends React.Component{
render(){
return(
<div className="game">
<div className="game-board">
<Board/>
</div>
</div>
)
}
}
ReactDOM.render(
<Game />,
document.querySelector('#root')
)
</script>
</body>
</html>
'node.js > react' 카테고리의 다른 글
React-Router_DOM(작성중) (0) | 2022.05.02 |
---|---|
react 커스텀 훅 (0) | 2022.04.26 |
React 댓글기능(작성중) (0) | 2022.04.25 |
[React] Component, props, State (0) | 2022.04.13 |
React (0) | 2022.04.12 |