
본 프로젝트는 리액트를 공부하기 위해 zerocho님의 인프런 강의를 듣고 따라 구현해본 프로젝트이다.
https://github.com/whanhee97/Minesweeper/tree/master/src
MineSearch.jsx
const initialState = {
tableData: [],
timer: 0,
result: '',
}
export const START_GAME = 'START_GAME';
const reducer = (state, action) => {
switch (action.type) {
case START_GAME:
return {
...state,
tableData: plantMine(action.row, action.cell, action.mine)
};
default:
return state;
}
}
initialState와 reducer 함수를 위와 같이 만들어 준다.
// 초기값 세팅
export const TableContext = createContext({
tableData: [],
dispatch: () => {},
});
createContext를 통해 컨텍스트를 만들고 초기화 해준다.
const MineSearch = () => {
const [state, dispatch] = useReducer(reducer, initialState);
//useMemo로 캐싱을 해줘야 contextAPI 사용시 계속되는 렌더링을 막을 수 있다.
const value = useMemo(() => ({ tableData: state.tableData, dispatch }), [state.tableData]);
return (
//value = {{ tableData: state.tableData, dispatch }} 원래는 이렇게 들어가지만 useMemo로 캐싱해줌
<TableContext.Provider value = {value}>
<Form />
<div>{state.timer}</div>
<Table />
<div>{state.result}</div>
</TableContext.Provider>
)
}
MineSearch 컴포넌트는 useReducer를 써서 state를 초기화 해줄 수 있고 dispatch로 상태를 변화시켜줄 수 있다.
그리고 컨텍스트로 값을 받을 컴포넌트들은 모두 Provider로 묶어준다. 이때 값은 value로 들어가고
useMemo로 캐싱을 해줘야 contextAPI 사용시 계속되는 렌더링을 막을 수 있다.
Form.jsx
import React, {useState, useCallback, useContext} from 'react'
import { TableContext, START_GAME } from './MineSearch'
const Form = () => {
const [row, setRow] = useState(10); // 줄(세로)
const [cell, setCell] = useState(10); // 칸(가로)
const [mine, setMine] = useState(20); // 지뢰 개수
const { dispatch }= useContext(TableContext);
// useCallback으로 감싸주면 불필요한 렌더링 막아줌
const onChangeRow = useCallback((e) => {
setRow(e.target.value);
}, []);
const onChangeCell = useCallback((e) => {
setCell(e.target.value);
}, []);
const onChangeMine = useCallback((e) => {
setMine(e.target.value);
}, []);
const onClickBtn = useCallback(() => {
dispatch({ type: START_GAME, row, cell, mine});
}, [row, cell, mine]);
return (
<div>
<input type="number" placeholder="세로" value={row} onChange={onChangeRow} />
<input type="number" placeholder="가로" value={cell} onChange={onChangeCell} />
<input type="number" placeholder="지뢰" value={mine} onChange={onChangeMine} />
<button onClick={onClickBtn}>시작</button>
</div>
)
}
export default Form;
Form 컴포넌트는 input 3개와 시작 버튼이 있고 각각의 함수는 useCallback으로 감싸줘서 성능을 최적화 한다.
MineSearch에서 export했던 TableContext에서 dispatch를 받아와 액션을 넘길 수 있다. 여기서는 게임시작 액션객체를 넘기고 그 안에는 row, cell, mine 값이 들어있다.
Table.jsx
import React, { useContext } from 'react'
import { TableContext } from './MineSearch'
import Tr from './Tr'
const Table = () => {
const { tableData } = useContext(TableContext);
return (
<table>
{Array(tableData.length).fill().map((tr, i) => <Tr rowIndex={i} />)}
</table>
)
}
export default Table;
Table 컴포넌트는 Tr 컴포넌트를 자식으로 가지고 있고 rowIndex를 props로 넘긴다(Td에서 현재 인덱스 값을 파악하기 위해)
Tr.jsx
import React, { useContext } from 'react'
import { TableContext } from './MineSearch'
import Td from './Td'
const Tr = ({ rowIndex }) => {
const { tableData } = useContext(TableContext);
return (
<tr>
{tableData[0] && Array(tableData[0].length).fill().map((td, i) => <Td rowIndex={ rowIndex } cellIndex={i} />)}
</tr>
)
}
export default Tr;
Tr 은 Td를 자식으로 가지고 rowIndex와 cellIndex를 넘긴다.
tableData[0] && 는 혹시 데이터가 없을 경우를 대비해서 넣음
Td.jsx
import React, { useContext } from 'react'
import { CODE, TableContext } from './MineSearch';
const Td = ({ rowIndex, cellIndex }) => {
const { tableData } = useContext(TableContext);
return (
<td>
{tableData[rowIndex][cellIndex]}
</td>
)
}
export default Td;
Td는 받아온 rowIndex와 cellIndex로 저렇게 표시한다.
useContext를 사용하면 저렇게 props를 통해 데이터를 받지 않아도 tableData를 한 번에 가져올 수 있다.