리액트 hooks로 구현한
틱택토 게임 입니다.
틱택토 게임 입니다.
예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.
순수 자바스크립트로 만든 로또 게임은 아래 포스트로 이동해주세요.
https://goddino.tistory.com/102
https://goddino.tistory.com/113
컴포넌트 구조
Tictactoe.jsx >Table.jsx > Tr.jsx > Td.jsx
· state 데이터는 상위 Tictactoe.jsx에서 관리하고, 클릭이벤트는 가장 하위 컴포넌트, Td.jsx에서 합니다.
· state 데이터는 props를 통해 상위에서 하위로 전달됩니다.
client.jsx
import React from 'react';
import ReactDom from 'react-dom';
import Tictactoe from './Tictactoe';
ReactDom.render(<Tictactoe />, document.querySelector('#root'));
index.html
<!DOCTYPE html>
<html lang="ko">
<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>틱택토</title>
<style>
table { border-collapse: collapse; }
td { width: 60px; height: 60px; border: 1px solid #666; font-size: 30px; text-align: center;}
</style>
</head>
<body>
<div id="root"></div>
<script src="./dist/app.js"></script>
</body>
</html>
Tictactoe.jsx - hooks 방식
import React, { useState, useEffect, useReducer, useCallback } from 'react';
import Table from './Table';
const initialState = {
turn: 'O',
winner: '',
tableData: [['','',''], ['','',''], ['','','']], //2차원 빈 배열
recentCell: [-1, -1], //최근 클릭한 셀, 초기화로 없는 셀을 넣음
}
export const SET_WINNER = 'SET_WINNER';
export const CLICK_CELL = 'CLICK_CELL'; //export로 모듈화, td 컴포넌트에서 재사용
export const CHANGE_TURN = 'CHANGE_TURN';
export const RESET_GAME = 'RESET_GAME';
const reducer = (state, action) => {
switch(action.type) {
case SET_WINNER: {
//state.winner = action.winner는 안됨, 새로운 action 객체를 만들어서 바뀐 값만 바꿔야함
return {
//새로운 state를 만들고 바뀌는 부분만 바꿔줌
...state, //spread 문법 = 새롭게 얕은 복사(참조)
winner: action.winner, //바뀌는 부분
}
}
case CLICK_CELL: {//찾은 위치에 턴 표시
const tableData = [...state.tableData]; //기존 tableData를 새롭게 얕은 복사
//td의 dispatch 안 action.row, 객체가 있으면 얕은 복사함
tableData[action.row] = [...tableData[action.row]];
tableData[action.row][action.cell] = state.turn;
return {
...state,
//바뀐 값, 불변성 때문에 객체는 spread 문법으로 복사
//tableData[action.row] = [...tableData[action.row]];
// tableData[action.row][action.cell] = state.turn;
tableData,
recentCell: [action.row, action.cell], //최근 클릭한 셀, 아래 빙고 확인 위해 추가
}
}
case CHANGE_TURN: { //턴 O X 교체
return {
...state,
turn: state.turn === 'O'? 'X': 'O',
}
}
case RESET_GAME: { //초기화
return {
...state,
turn: 'O',
tableData: [['','',''], ['','',''], ['','','']],
recentCell: [-1, -1],
};
}
default:
return state;
}
}
const Tictactoe = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const {tableData, turn , winner, recentCell} = state;
useEffect(() => { //빙고 확인 코드-8가지 경우의 수 체크
//셀 클릭 -> reducer의 CLICK_CELL 실행 -> 클릭한 셀을 recentCell에 저장
//-> recentCell이 바뀔때마다 useEffect 작동
const [row, cell] = recentCell;
if(row < 0) {
return;
}
let win = false; //승자 없음
if(tableData[row][0] === turn && tableData[row][1] === turn && tableData[row][2] === turn) {
win = true; //가로줄 검사
}
if(tableData[0][cell] === turn && tableData[1][cell] === turn && tableData[2][cell] === turn) {
win = true; //세로줄 검사
}
if(tableData[0][0] === turn && tableData[1][1] === turn && tableData[2][2] === turn) {
win = true; //대각선 검사
}
if(tableData[0][2] === turn && tableData[1][1] === turn && tableData[2][0] === turn) {
win = true; //대각선 검사
}
console.log(win, row, cell, turn);
if(win) { //이겼을때(빙고)
dispatch({type: SET_WINNER, winner: turn}); //현재 turn이 승자
dispatch({type: RESET_GAME}); //초기화
}else{ //무승부 검사
let all = true; //모든 칸 참(가정) -> 무승부 의미
tableData.forEach((row) => {row.forEach((cell) => { //이차원 배열 forEach()
if(!cell) { //하나라도 안찼으면
all = false; //false로 변경
}
});
});
if(all){ //무승부
dispatch({type: RESET_GAME});
}else{
dispatch({type: CHANGE_TURN}); //턴 넘기기
}
}
}, [recentCell]); //recentCell가 바뀔때마다 실행
return (
<div>
{/* <Table onClick={onClickTable} tableData={tableData} dispatch={dispatch} /> */}
<Table tableData={tableData} dispatch={dispatch} />
{winner && <div>{winner}님의 승리입니다.</div>}
</div>
);
}
export default Tictactoe;
Table.jsx
import React from 'react';
import Tr from './Tr';
const Table = ({onClick, tableData, dispatch}) => {
return (
<table onClick={onClick}>
<tbody>
{Array(tableData.length).fill().map( (tr, i) =>
(<Tr key={i} rowIndex = {i} rowData = {tableData[i]} dispatch={dispatch} />))}
{/* rowData = ['','',''], tableData를 rowData로 넘김, i = 줄번호 */}
</tbody>
</table>
);
}
export default Table;
Tr.jsx
import React, {useMemo} from 'react';
import Td from './Td';
const Tr = ({rowData, rowIndex, dispatch}) => {
return (
<tr>
{/* 클릭한 td의 위치를 알기 위해 필요 -> rowIndex: 줄수, cellIndex: 칸수 */}
{Array(rowData.length).fill().map( (v, i) =>
useMemo(
() => <Td key={i} dispatch={dispatch} rowIndex={rowIndex} cellIndex={i}
cellData={rowData[i]}/>,
[rowData[i]]
)
)}
{/* rowData를 cellData로 Td에 보내줌 */}
</tr>
);
};
export default Tr;
Td.jsx
import React, {useCallback} from 'react';
import {CLICK_CELL} from './Tictactoe'
const Td = ({rowIndex, cellIndex, dispatch, cellData}) => { //dispatch를 Tictactoe부터 Td까지 props로 넘김
const onClickTd = useCallback( () => {
//console.log(rowIndex, cellIndex); //몇번째 줄, 칸의 위치를 알수 있음
if(cellData) { //이미 클릭했을 경우, 재클릭 방지
return;
}
//CLICK_CELL action dispatch, 클릭한 td의 위치 전달(row, cell)
dispatch({type: CLICK_CELL, row: rowIndex, cell: cellIndex});
}, [cellData]); //cellData를 바뀌는 값
return (
<td onClick={onClickTd}>{cellData}</td>
);
}
export default Td;
반응형
'개발 > React' 카테고리의 다른 글
[react] 리액트 테이블 게시판 만들기 ver.1 (axios, useEffect, 글삭제) (8) | 2021.04.13 |
---|---|
[react] react의 useReducer 사용법 (0) | 2021.04.06 |
[js] react로 로또 게임 만들기 (0) | 2021.04.01 |
[react] react로 가위바위보 게임 만들기 (0) | 2021.04.01 |
[react] react로 반응속도 테스트 게임 만들기(ft. useRef, 조건문, spread 연산자) (0) | 2021.03.24 |
댓글