useState로 상태관리를 해주었지만 useState말고도 useReducer를 이용한 상태관리가 가능하다.
useReducer를 사용하면 컴포넌트의 상태 업데이트를 다른 파일에 불러와서 사용할 수도 있고, 컴포넌트 바깥에서 사용할 수도 있다.
const initialState = {
winner: '',
turn: 'O',
tableData: [
['', '', ''],
['', '', ''],
['', '', '']
],
recentCell: [-1, -1],
};
export const SET_WINNER = 'SET_WINNER';
export const CLICK_CELL = 'CLICK_CELL';
export const CHANGE_TURN = 'CHANGE_TURN';
export const RESET_GAME = 'RESET_GAME';
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;
}
};
const TicTacToe = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const { tableData, turn, winner, recentCell } = state;
const onClickTable = useCallback(() => {
dispatch({ type: 'SET_WINNER', winner: 'O' }); //액션개체
}, []);
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;
}
if (win) { //승리시
dispatch({ type: SET_WINNER, winner: turn });
dispatch({ type: RESET_GAME })
} else { //무승부
let all = true; // -> all이 true면 무승부라는 뜻
tableData.forEach((row) => {
row.forEach((cell) => {
if (!cell) {
all = false;
}
})
})
if (all) {
} else {
dispatch({ type: CHANGE_TURN });
}
}
}, [recentCell]);
return (
<>
<Table onClick={onClickTable} tableData={tableData} dispatch={dispatch} />
{winner && <div>{winner}님의 승리</div>}
</>
)
}
const reducer = (state, action) => {
switch (action.type) {
case SET_WINNER:
//state.winner = action.winner 이렇게 하면 안됨. 복사해서 바꿔줘야됨.
return {
...state,
winner: action.winner,
};
위 예제에서 state.winner = action.winner로 써주게 되면 리액트의 불변성을 해치게 되서 오류가 날 수 있다.
...state, winner : action.winner 로 작성해줘야 된다.
const initialState = {
winner: '',
turn: 'O',
tableData: [
['', '', ''],
['', '', ''],
['', '', '']
],
recentCell: [-1, -1],
};
Type값은 대문자와 _ 로 이루어지지만 관습일 뿐 꼭 그렇게 지켜져야하는 것은 아니다.