리액트 토이 프로젝트 - 틱택토 게임 만들기

REASON·2022년 9월 20일
0

토이 프로젝트

목록 보기
6/9

제로초님 강의 보고 useReducer 배운거 복습겸 혼자 만들어봤다.
물론 솔루션 코드 계속 봐가면서 만든거라 혼자 만들었다고 하기는 좀 그렇지만..ㅠㅠ

const tableData = [...state.tableData];
      tableData[action.row] = [...tableData[action.row]];
      tableData[action.row][action.cell] = state.turn;

불변성을 유지하는 부분이 가장 혼란스러웠다. (머리로는 아는데 손이 안 따라줘...!)

1차원 배열 같은 경우는 스프레드 문법 1번이면 충분하지만, 이번 틱택토에서는 2차원 배열을 사용했기 때문에 2번이나 스프레드 문법을 사용해야 했었다.

immer 라이브러리를 사용하면 저 부분은 해결할 수 있다고 하시더라.

그리고 같은 O가 대각선, 가로, 세로로 3개가 모였는지 확인하는 로직하고 무승부 처리하는 코드는 조금 더 코드를 공부해야겠다고 생각했다.

최적화

컴포넌트 최적화를 하는 방법도 틱택토를 만들면서 배울 수 있었는데, 보통 반복문을 사용하는 컴포넌트가 있다면 memo로 감싸주는 편이 좋다고 한다.
memo를 사용하면 props가 바뀌기 전까지는 컴포넌트가 리렌더링 되지 않는다.
memo로도 최적화가 되지 않는다면 useMemo를 사용해서 컴포넌트 자체를 기억시키는 방법도 있다고 배웠다.

useReducer는 리덕스와 달리 비동기이다.

리덕스를 사용해보진 못했지만 useRedcuer는 비동기로 돌아간다고 한다.
그래서 현재 turn을 기준으로 승자를 체크하는 경우 다음 턴으로 이미 넘어간 상태가 되는 문제가 있어서 recentCell 이라는 상태가 필요하다는 것을 배웠다.

TicTacToe 컴포넌트의 useEffect 코드는 카피해왔기 때문에 다시 만들어볼 것!

도저히 어떻게 했더라..? 어떻게 해야되지?가 안 떠올라서 결국 코드를 복붙해왔다.. ㅋㅋ

전체 코드

/*TicTacToe.jsx*/
import React, { useEffect, useReducer } from 'react';
import Table from './Table';

const initialState = {
  winner: '',
  turn: 'O',
  tableData: [
    ['', '', ''],
    ['', '', ''],
    ['', '', ''],
  ],
  recentCell: [-1, -1],
};

export const CLICK_CELL = 'CLICK_CELL';
export const CHANGE_TURN = 'CHANGE_TURN';
export const RESET_GAME = 'RESET_GAME';
export const SET_WINNER = 'SET_WINNER';

const reducer = (state, action) => {
  switch (action.type) {
    case SET_WINNER:
      return {
        ...state,
        winner: action.winner,
      };

    case CLICK_CELL:
      const tableData = [...state.tableData];
      tableData[action.row] = [...tableData[action.row]];
      tableData[action.row][action.cell] = state.turn;

      return {
        ...state,
        tableData,
        recentCell: [action.row, action.cell],
      };
    case CHANGE_TURN:
      return {
        ...state,
        turn: state.turn === 'O' ? 'X' : 'O',
      };
    case RESET_GAME:
      return {
        ...state,
        turn: 'O',
        tableData: [
          ['', '', ''],
          ['', '', ''],
          ['', '', ''],
        ],
        recentCell: [-1, -1],
      };
    default:
      return state;
  }
};

function TicTacToe() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { tableData, winner, recentCell, turn } = state;

  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);

    if (win) {
      dispatch({
        type: SET_WINNER,
        winner: turn,
      });

      dispatch({
        type: RESET_GAME,
      });
    } else {
      let all = true;

      tableData.forEach((row) => {
        row.forEach((cell) => {
          if (!cell) {
            all = false;
          }
        });
      });

      if (all === false) {
        dispatch({
          type: CHANGE_TURN,
        });
      } else {
        dispatch({
          type: RESET_GAME,
        });
      }
    }
  }, [recentCell]);

  return (
    <div className="ttc-container">
      <h2 className="ttc-title">틱택토 게임</h2>
      <Table tableData={tableData} dispatch={dispatch}></Table>
      {winner && <p className="ttc-text">🎉 {winner}님의 승리입니다. </p>}
    </div>
  );
}

export default TicTacToe;
/*Table.jsx*/
import React from 'react';
import Tr from './Tr';

function Table({ tableData, dispatch }) {
  return (
    <table className="ttc-table">
      <tbody>
        {tableData.map((row, i) => {
          return <Tr key={`${i}번째 tr`} rowData={row} rowIndex={i} dispatch={dispatch}></Tr>;
        })}
      </tbody>
    </table>
  );
}

export default Table;
/*Tr.jsx*/
import React, { useCallback, memo } from 'react';
import Td from './Td';

function Tr({ rowData, rowIndex, dispatch }) {
  return (
    <tr>
      {rowData.map((cell, i) => {
        return <Td key={`${i}번째 td`} dispatch={dispatch} cellData={cell} cellIndex={i} rowIndex={rowIndex}></Td>;
      })}
    </tr>
  );
}

export default memo(Tr);
/*Td.jsx*/
import React, { useCallback, memo } from 'react';
import { CLICK_CELL } from './TicTacToe';

function Td({ cellData, cellIndex, dispatch, rowIndex }) {
  const clickCell = useCallback(() => {
    if (cellData) return;

    dispatch({
      type: CLICK_CELL,
      row: rowIndex,
      cell: cellIndex,
    });
  }, [cellData]);

  return (
    <td className="ttc-ox" onClick={clickCell}>
      {cellData}
    </td>
  );
}

export default memo(Td);


참고 자료
제로초 틱택토

0개의 댓글