Today I Learned ... react.js
🙋♂️ React.js Lecture
🙋 My Dev Blog
React.createContext
export하여 하위 컴포넌트에서도 사용 가능하게.
-> ()안에 초기값을 작성해준다.
useContext
import한 context명을 작성해준다.
<Provider value={value}>
React Lecture CH 8
1 - context API
2 - createContext와 Provider
3 - useContext
4 - 좌클릭, 우클릭 로직
5 - 지뢰 개수 표시
6 - 빈칸 한번에 열기
7 - 승리 조건 체크, 타이머
8 - context API 최적화
MineSearch.jsx
const reducer = (state, action) => {
switch (action.type) {
case START_GAME:
return {
...state,
tableData: plantMine(action.row, action.cell, action.mine),
};
// 🔻 추가
case OPEN_CELL: {
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
tableData[action.row][action.cell] = CODE.OPENED;
return {
...state,
tableData,
};
}
default:
return state;
}
};
Td.jsx
const Td = ({ rowIndex, cellIndex }) => {
const { tableData, dispatch } = useContext(TableContext);
const onClickTd = useCallback(() => {
dispatch({ type: OPEN_CELL, row: rowIndex, cell: cellIndex });
}, []);
return (
<td style={getTdStyle(tableData[rowIndex][cellIndex])} onClick={onClickTd}>
{getTdText(tableData[rowIndex][cellIndex])}
</td>
);
};
onClickTd
함수를 작성함. useCallback
사용!<결과>
Td를 클릭시 하얀색으로 변함.
즉, tableData[rowIndex][cellIndex]에 CODE.OPENED
가 대입되어, getTdStyle에 의해 background가 #fff
가 되는 것.
해결할 점
- 지뢰가 있는 부분도 하얗게 됨
- 숫자가 나와야 함
tableData[rowIndex][cellIndex]
의 값에 따라서 동작이 달라져야함.Td.jsx
const onClickTd = useCallback(() => {
switch (tableData[rowIndex][cellIndex]) {
case CODE.OPENED:
case CODE.FLAG_MINE:
case CODE.FLAG:
case CODE.QUESTION_MINE:
case CODE.QUESTION:
return;
case CODE.NORMAL:
dispatch({ type: OPEN_CELL, row: rowIndex, cell: cellIndex });
return;
case CODE.MINE:
dispatch({ type: CLICK_MINE, row: rowIndex, cell: cellIndex });
return;
}
}, [tableData[rowIndex][cellIndex]]);
Td 컴포넌트의 getTdStyle, getTdText를 수정해보자.
const getTdStyle = (code) => {
switch (code) {
case CODE.NORMAL:
case CODE.MINE:
return {
background: '#444',
};
case CODE.CLICKED_MINE:
case CODE.OPENED:
return {
background: '#fff',
};
case CODE.QUESTION:
case CODE.QUESTION_MINE:
return {
background: 'yellow',
};
case CODE.FLAG:
case CODE.FLAG_MINE:
return {
background: 'red',
};
default:
return {
background: '#fff',
};
}
};
const getTdText = (code) => {
switch (code) {
case CODE.NORMAL:
return '';
case CODE.MINE:
return 'X';
case CODE.CLICKED_MINE:
return '펑';
case CODE.FLAG:
case CODE.FLAG_MINE:
return '!';
case CODE.QUESTION:
case CODE.QUESTION_MINE:
return '?';
default:
return '';
}
};
Td.jsx
const onRightClickTd = useCallback(
(e) => {
e.preventDefault();
switch (tableData[rowIndex][cellIndex]) {
case CODE.NORMAL:
case CODE.MINE:
dispatch({ type: FLAG_CELL, row: rowIndex, cell: cellIndex });
return;
case CODE.FLAG:
case CODE.FLAG_MINE:
dispatch({ type: QUESTION_CELL, row: rowIndex, cell: cellIndex });
return;
case CODE.QUESTION:
case CODE.QUESTION_MINE:
dispatch({ type: NORMALIZE_CELL, row: rowIndex, cell: cellIndex });
return;
default:
return;
}
},
[tableData[rowIndex][cellIndex]]
);
// JSX
return (
<td
style={getTdStyle(tableData[rowIndex][cellIndex])}
onClick={onClickTd}
onContextMenu={onRightClickTd}
>
{getTdText(tableData[rowIndex][cellIndex])}
</td>
);
tableData[rowIndex][cellIndex]
의 값에 의해 동작이 달라져야 하므로, switch문 작성.MineSearch.jsx
...
export const START_GAME = 'START_GAME';
export const OPEN_CELL = 'OPEN_CELL';
export const CLICK_MINE = 'CLICK_MINE';
export const FLAG_CELL = 'FLAG_CELL';
export const QUESTION_CELL = 'QUESTION_CELL';
export const NORMALIZE_CELL = 'NORMALIZE_CELL';
추가 state -
halted
- 지뢰가 터졌을때 게임을 일시정지하는 state.
const initialState = { tableData: [], timer: 0, result: '', halted: false, // 👈 추가 };
case CLICK_MINE: {
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
tableData[action.row][action.cell] = CODE.CLICKED_MINE;
return {
...state,
tableData,
halted: true,
};
}
halted
를 true로 하여 게임이 중단되게 한다.
단, START_GAME 액션에 return 부분에 halted: false를 추가하여 게임을 다시 시작할 수 있도록.
case START_GAME:
return {
...state,
tableData: plantMine(action.row, action.cell, action.mine),
halted: false,
};
halted를 initialState와 creactContext에도 추가해주고,
Td 컴포넌트에서 useContext()로 불러와준다.
MineSearch.jsx
export const TableContext = createContext({
tableData: [],
halted: true,
dispatch: () => {},
});
const initialState = {
tableData: [],
timer: 0,
result: '',
halted: true,
};
Td.jsx
const { tableData, dispatch, halted } = useContext(TableContext);
halter
가 true면 셀 클릭 못하게 (게임 중단)const onClickTd = useCallback(() => {
if (halted) {
return;
}
...
}
const onRightClickTd = useCallback(
(e) => {
e.preventDefault();
if (halted) {
return;
}
...
}
MineSearch.jsx
case FLAG_CELL: {
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
if (tableData[action.row][action.cell] === CODE.MINE) {
tableData[action.row][action.cell] = CODE.FLAG_MINE;
} else {
tableData[action.row][action.cell] = CODE.FLAG;
}
return {
...state,
tableData,
};
}
case QUESTION_CELL: {
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
if (tableData[action.row][action.cell] === CODE.FLAG_MINE) {
tableData[action.row][action.cell] = CODE.QUESTION_MINE;
} else {
tableData[action.row][action.cell] = CODE.QUESTION;
}
return {
...state,
tableData,
};
}
case NORMALIZE_CELL: {
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
if (tableData[action.row][action.cell] === CODE.QUESTION_MINE) {
tableData[action.row][action.cell] = CODE.MINE;
} else {
tableData[action.row][action.cell] = CODE.NORMAL;
}
return {
...state,
tableData,
};
}
- 우선, 원본이 변하면 안되므로, 얕은 복사를 통해 tableData를 복사한다.
- tableData[action.row]도 얕은 복사 한다.
(이차원 배열이므로 펼쳐줘야함)- if-else 로직으로 일반 셀인지 (NORMAL), 지뢰 셀인지 (MINE) 체크해서 구분해줌.
빈 칸을 누르면 주변에 지뢰가 몇 개가 있는지 나타내야 함
-> OPEN_CELL의 reducer에 작성.
빈칸만 여러개 있는 곳을 눌렀을 때는, 주변 칸까지 한번에 열리게
-> 재귀를 통해 구현.
MineSearch.jsx
case OPEN_CELL: {
const tableData = [...state.tableData];
tableData[action.row] = [...state.tableData[action.row]];
tableData[action.row][action.cell] = CODE.OPENED;
let around = [];
if (tableData[action.row - 1]) {
around = around.concat(
tableData[action.row - 1][action.cell - 1],
tableData[action.row - 1][action.cell],
tableData[action.row - 1][action.cell + 1]
);
}
around = around.concat(
tableData[action.row][action.cell - 1],
tableData[action.row][action.cell + 1]
);
if (tableData[action.row + 1]) {
around = around.concat(
tableData[action.row + 1][action.cell - 1],
tableData[action.row + 1][action.cell],
tableData[action.row + 1][action.cell - 1]
);
}
const count = around.filter((v) =>
[CODE.MINE, CODE.FLAG_MINE, CODE.QUESTION_MINE].includes(v)
).length;
console.log(around, count);
tableData[action.row][action.cell] = count;
return {
...state,
tableData,
};
}
const getTdText = (code) => {
switch (code) {
...
default:
return code || '';
}
다음시간 - 빈칸 한번에 열리게 (재귀함수)