React - 지뢰찾기 만들기(1)

이환희·2021년 5월 10일

React

목록 보기
5/12


본 프로젝트는 리액트를 공부하기 위해 zerocho님의 인프런 강의를 듣고 따라 구현해본 프로젝트이다.
https://github.com/whanhee97/Minesweeper/tree/master/src

필요한 컴포넌트

  • App
    • MineSearch
      • Form
      • timer (컴포넌트 아님)
      • Table
        • Tr
          • Td
      • result (컴포넌트 아님)

Step 1

contextAPI 와 리듀서 함수로 유사 리덕스 처럼 활용하기

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를 한 번에 가져올 수 있다.

0개의 댓글