useState를 대체하는 상태관리 hook으로 써 다수의 state를 다루는데 유리하다.
- 첫번째 매개변수 : reducer 함수
- 두번째 매개변수 : initialState
- 첫번째 매개변수 : state
- 두번째 매개변수 : action
컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있습니다.
상태 업데이트 로직을 컴포넌트 바깥에 작성 할 수도 있고, 심지어 다른 파일에 작성 후 불러와서 사용 할 수 있다.
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
initialState는 useReducer에 의해 상태를 등록하고
state를 통해 상태를 접근 할 수 있고
dispatch를 통해 dispatch안의 내용에 따라 action을 실행할 수 있다.
현재는 useReducer에 의해 만들어진 상태 state와 dispatch를
return 아래 필요한 부분에서 state를 통해 값을 보여주고
dispatch를 통해 이벤트의 action을 type에 따라 실행시켜 리턴해주는 역할을 한다.
import React, { useEffect, useReducer, useCallback } from 'react';
import Table from './Table';
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:
// state.winner = action.winner; 이렇게 하면 안됨.
return {
...state,
winner: action.winner,
};
case CLICK_CELL: {
const tableData = [...state.tableData];
tableData[action.row] = [...tableData[action.row]]; // immer라는 라이브러리로 가독성 해결
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 [winner, setWinner] = useState('');
// const [turn, setTurn] = useState('O');
// const [tableData, setTableData] = useState([['', '', ''], ['', '', ''], ['', '', '']]);
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;
}
console.log(win, row, cell, tableData, turn);
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) {
dispatch({ type: SET_WINNER, winner: null });
dispatch({ type: RESET_GAME });
} else {
dispatch({ type: CHANGE_TURN });
}
}
}, [recentCell]);
return (
<>
<Table onClick={onClickTable} tableData={tableData} dispatch={dispatch} />
{winner && <div>{winner}님의 승리</div>}
</>
)
};
export default TicTacToe;
다른 점은 reducer 함수의 기존에 state에 다른 것을 추가시켜주는
{ ...state , 추가내용}
형태로 만들어져있다.