개발노트

14 (3)context API 본문

React/basic

14 (3)context API

aloha2jh 2020. 7. 30. 13:32

칸 열었을때 지뢰 갯수 표시해주기

(1) 주변에 지뢰가 있는지 검사하기 위해 around 배열에 주변 칸(상태값)들 담아줌 !

            let around = [];
            if(tableData[action.trIndex-1]){  
                around = around.concat(
                    tableData[action.trIndex-1][action.tdIndex -1],
                    tableData[action.trIndex-1][action.tdIndex ],
                    tableData[action.trIndex-1][action.tdIndex +1],
                )
            }
            around = around.concat(
                tableData[action.trIndex][action.tdIndex -1],
                tableData[action.trIndex][action.tdIndex +1]
            );
            if(tableData[action.trIndex +1]){  
                around =around.concat(
                    tableData[action.trIndex+1][action.tdIndex -1],
                    tableData[action.trIndex+1][action.tdIndex ],
                    tableData[action.trIndex+1][action.tdIndex +1],
                );
            }

윗끝줄은 윗칸이 없기때문에 if 문으로나눴음

 

불변성 유지하기 위해  concat으로 복사하면서 담는다.

let arr = [];
arr = arr.concat( 1,2,3 ); // ?
let arr = [];
arr = arr.concat( 1,2,3 ); // [1,2,3]

 

 

filter안에 배열안에 포함되어있는지 검사해서 length반납해주는.. 패턴?을사용

const arr = [1,2,3,4,5,6];

arr.includes(1) // ?
arr.includes(2) // ?

arr.filter( (v)=> ( [1,2].includes(v) ) ).length  // ?
const arr = [1,2,3,4,5,6];

arr.includes(1) //true
arr.includes(2) //true

arr.filter( (v)=> ( [1,2].includes(v) ) ).length // 2

 

 

 

 

(2)지뢰가 있는지 검사

const mineCount = around.filter((v)=>( [TD_STATE.MINE, TD_STATE.FLAG_BUT, TD_STATE.QUESTION_BUT].includes(v) )).length;
//const mineCount = around.filter((v)=>( [-7, -5, -4].includes(v) )).length;
//tableData[action.trIndex][action.tdIndex] = TD_STATE.OPENED;
tableData[action.trIndex][action.tdIndex] = mineCount;

구한 지뢰갯수를 클릭한 칸에 전달.

 

 

주변에 지뢰가 있는지 잘 알려줌

 

const tdText = ( stateCode ) =>{ 
    switch(stateCode){
        case TD_STATE.NORMAL:
            return '';
        case TD_STATE.MINE:
            return 'X';
        case TD_STATE.OPENED_MINE:
            return '펑!';
        case TD_STATE.FLAG_BUT:
        case TD_STATE.FLAG:
            return '!~'
        case TD_STATE.QUESTION:
            return '?'
        default: 
            return stateCode || ''; ////
    }

}

tableData에서 열었을때 상태코드를 지뢰갯수mineCount로  set해줬기 때문에,

텍스트 처리 하는 값에서 그냥 상태코드 그대로 보여주는걸로 해준다

0일경우 표시안해주려고 '' 비어있는스트링 리턴

        case OPEN_TD:{
            const tableData = [...state.tableData];
            tableData[action.trIndex] = [...state.tableData[action.trIndex]];
            
            // (1) 주변 칸(상태값)들 담아줌 
            let around = [];
            if(tableData[action.trIndex-1]){  
                around = around.concat(
                    tableData[action.trIndex-1][action.tdIndex -1],
                    tableData[action.trIndex-1][action.tdIndex ],
                    tableData[action.trIndex-1][action.tdIndex +1],
                )
            }
            around = around.concat(
                tableData[action.trIndex][action.tdIndex -1],
                tableData[action.trIndex][action.tdIndex +1]
            );
            if(tableData[action.trIndex +1]){  
                around =around.concat(
                    tableData[action.trIndex+1][action.tdIndex -1],
                    tableData[action.trIndex+1][action.tdIndex ],
                    tableData[action.trIndex+1][action.tdIndex +1],
                );
            } 
            
            // (2) 지뢰가 있는지 검사  
            const mineCount = around.filter((v)=>( [TD_STATE.MINE, TD_STATE.FLAG_BUT, TD_STATE.QUESTION_BUT].includes(v) )).length;
                console.log( tableData[action.trIndex][action.tdIndex] ); 
                console.log( around, mineCount );
            tableData[action.trIndex][action.tdIndex] = mineCount;
            //tableData[action.trIndex][action.tdIndex] = TD_STATE.OPENED;
            return{
                ...state,
                tableData,
            }
        }   

 

 

 

 

 

 

 

칸 열었을때 주변의 여러개칸 같이 열리게

주변 지뢰가 하나도 없을때 주변칸들을 연다

 

 

 

한칸만열릴경우는- 내가 여는 칸만 값 가져와서 바꾸면 됬었는데, ->내가 여는 한줄만 복사

(0)주변칸도 열어야 되니까 불변성유지위해 ->전체줄 다 복사 

모든 줄을 새로운 객체tableData 에 담아줌

            const tableData = [...state.tableData];
            tableData.forEach( (tr, i) =>{
                tableData[i] = [...state.tableData[i]];
            });

 

(1) 함수로 묶어서 호출.

        case OPEN_TD:{
            const tableData = [...state.tableData];
            tableData.forEach( (tr, i) =>{  // (0)
                tableData[i] = [...state.tableData[i]];
            });
            
            //내 tr,td 값으로 주변칸 검사해주는 함수
            
            const checkAndOpen=(tr, td)=>{ //(1)

                // (1) 주변 칸(상태값)들 담아줌 
                let around = [];
                if(tableData[tr]-1){  
                    around = around.concat(
                        tableData[tr-1][td-1],
                        tableData[tr-1][td],
                        tableData[tr-1][td+1],
                    )
                }
                around = around.concat(
                    tableData[tr][td-1],
                    tableData[tr][td+1],
                );
                if(tableData[tr +1]){  
                    around =around.concat(
                        tableData[tr+1][td-1],
                        tableData[tr+1][td],
                        tableData[tr+1][td+1],
                    );
                }
 

                // (2) 지뢰가 있는지 검사  - 지뢰갯수 를 누른칸에 보여줌
                const mineCount = around.filter((v)=>( [TD_STATE.MINE, TD_STATE.FLAG_BUT, TD_STATE.QUESTION_BUT].includes(v) )).length;
                tableData[tr][td] = mineCount; 

                
				////////
                if(mineCount === 0){ //내주변 지뢰가0이면 - 옆칸 다 검사 

 
                }
                 
            }

            checkAndOpen( action.trIndex, action.tdIndex );  //(1)

 

재귀 함수로 쓰기 위해서 기능들을 checkAndOpen이라는 함수로 담았고

action.trIndex-> tr , action.tdIndex ->td 인자로 받음

 

내 tr, td 를 넘겨줘서 내tr,td위치값 8개가 지뢰가 없을경우주변칸들 까지 계속 검사하는 함수로 만든다.

 

만약 내가 클릭했던 칸 주변으로 지뢰가 없을 경우.

내 tr, td 주변으로 하나씩 다 눌러주기 위한 배열목록 near 을 만든다

                if(mineCount === 0){ //내주변 지뢰가0이면 - 옆칸 다 검사해서 연다.

                    const near = [];
                    if( tr-1 > -1 ){ //첫째줄이여서 0-1 하면 -1 - 즉 첫째줄 일경우 
                        near.push([tr-1, td-1]); //near [-1, ]
                        near.push([tr-1, td]);   //near [-1, ]
                        near.push([tr-1, td+1]); // near [-1, ]
                    }
                    near.push([tr, td-1]);
                    near.push([tr, td+1]);
                    if( tr+1 < tableData.length ){ //마지막줄일 경우 아랫칸 없으니까, 아랫칸 없애준다(검사안한다.)
                        near.push([tr+1, td-1]);
                        near.push([tr+1, td]);
                        near.push([tr+1, td+1]); // near 
                    }
                    console.log(near);
                    near.filter(v=>!!v).forEach((n)=>{
                        if( tableData[n[0]][n[1]] !== TD_STATE.OPEND )
                        checkAndOpen(n[0],n[1]);
                    })

                } 
                tableData[tr][td] = mineCount; /// ?
               
            }

그리고 모서리일 경우 안열어야 하는데,

모서리일 경우는 -값으로 한정되게 됨 (0,0부터시작하니까.)

 

 

그래서 -1 이면 리턴시키는 조건을 초반부에 적어놓아서 없는칸(-1)은 return 시킨다.

    if( tr<0 || tr >= tableData.length || td<0 || td >= tableData[0].length ){
                    return; 
	}

 

+ 열린칸이면 꼭. return 시켜야 콜스택 터지는걸 막을수 있음;

(열린칸 안막으면 주변칸 서로가 서로를 계속 열어줘서 문제발생됨 재귀함수 조심)

if( [TD_STATE.OPENED, TD_STATE.QUESTION, TD_STATE.QUESTION_BUT, TD_STATE.FLAG, TD_STATE.FLAG_BUT].includes( tableData[tr][td]) ){
	return;
}

case OPEN_TD:{
            const tableData = [...state.tableData];
            tableData.forEach( (tr, i) =>{
                tableData[i] = [...state.tableData[i]];    //  ? 
                //tableData[i] = [...tr];
            }); 
            const checked = [];

            //내 tr,td 값으로 주변(max 8)칸 검사해서 열어주는 함수
            const checkAndOpen=(tr, td)=>{ 
                
                if( tr<0 || tr >= tableData.length || td<0 || td >= tableData[0].length ){
                    return; 
                } 
                //내tr,td값이 이미 열린칸이면 return. (지뢰칸이여도)
                if( [TD_STATE.OPENED, TD_STATE.QUESTION, TD_STATE.QUESTION_BUT, TD_STATE.FLAG, TD_STATE.FLAG_BUT].includes( tableData[tr][td]) ){
                    return;
                }
 
                //한번 체크해서 연 칸은 다시 체크하지 않기 위해 
                if( checked.includes(tr+','+td) ){ 
                    return;
                }else{
                    checked.push(tr+','+td);
                }

                // 1주변 칸(상태값)들 담아줌 
                let around = [
                    tableData[tr][td-1], tableData[tr][td+1]  // 좌 우는 무조건 담고. /어차피 없으면 undefined
                ];
                if(tableData[tr-1]){  // 내윗줄 있으면 담고
                    around = around.concat(
                        tableData[tr-1][td-1],
                        tableData[tr-1][td],
                        tableData[tr-1][td+1],
                    )
                }
                // around = around.concat(
                //     tableData[tr][td-1],
                //     tableData[tr][td+1],
                // );
                if(tableData[tr+1]){  //내아랫줄 있으면 담고
                    around =around.concat(
                        tableData[tr+1][td-1],
                        tableData[tr+1][td],
                        tableData[tr+1][td+1],
                    );
                }

                // 2지뢰가 있는지 검사  - 지뢰갯수 를 누른칸에 보여줌
                const mineCount = around.filter((v)=>( [TD_STATE.MINE, TD_STATE.FLAG_BUT, TD_STATE.QUESTION_BUT].includes(v) )).length;

                //tableData[tr][td] = mineCount; 
 
                if(mineCount === 0){ //내주변 지뢰가0이면 - 옆칸 다 검사해서 연다. 
                    const near = [];
                    if( tr-1 > -1 ){ //첫째줄이여서 0-1 하면 -1 - 즉 첫째줄 일경우 
                        near.push([tr-1, td-1]); //near [-1, ]
                        near.push([tr-1, td]);   //near [-1, ]
                        near.push([tr-1, td+1]); // near [-1, ]
                    }
                    near.push([tr, td-1]);
                    near.push([tr, td+1]);
                    if( tr+1 < tableData.length ){ //마지막줄일 경우 아랫칸 없으니까, 아랫칸 없애준다(검사안한다.)
                        near.push([tr+1, td-1]);
                        near.push([tr+1, td]);
                        near.push([tr+1, td+1]); // near 
                    }
                    console.log(near);
                    near.forEach((n)=>{
                        if( tableData[n[0]][n[1]] !== TD_STATE.OPEND )
                        checkAndOpen(n[0],n[1]);
                    }) 
                } 
                tableData[tr][td] = mineCount; /// ? 
            } 
            checkAndOpen( action.trIndex, action.tdIndex ); 
            return{
                ...state,
                tableData,
            }
        }   

 

 

 

 

 

승리조건

연칸이 == ( 칸갯수(가로x세로) - 지뢰갯수 ) 되면 승리.

 

checkAndOpen함수 실행시켜서 한번 실행때마다 칸을 총몇개열었는지 -> tdOpen(함수내)

initialState에 게임시작후 총 몇개 열었는지->opendCount (initialState)

테이블세팅 값들 -> tableInfo { row: 0, col:0, mine:0 } (initialState)

 

세가지 값이 필요하다

 

GAME_START 액션후 tableInfo 값을 넣어주고,

다른값들초기화

        case START_GAME:
            return{
                ...state,
                tableData: plantMine(action.row, action.col, action.mine),
                gameOver: false,
                opendCount:0,
                tableInfo: { row: action.row, col: action.col, mine: action.mine},
                result:''
            };

 

checkAndOpen함수로 총 몇개 열었는지 count 한다

 

                tableData[tr][td] = mineCount;   /////////
                
                tdOpen ++;  

mineCount넣어주는 곳에 (칸open)  tdOpen ++;

 

            if( ((state.tableInfo.row*state.tableInfo.col)-state.tableInfo.mine) === state.opendCount+tdOpen ){
                gameOver=true;
                result='승리';
            }
              
            console.log('opendCount:'+state.opendCount); 
 
            return{
                ...state,
                tableData,
                gameOver,
                opendCount: state.opendCount + tdOpen,
                result, 
            }

state.tableInfo에 넣어둔 row, col, mine값 에서 openedCount 값이 맞아지면, 승리 !

 

 

타이머

타이머는, setInterval 1초마다 실행으로 만들면 되는데,

 (()=>{ /* 숫자1씩증가state.timer+1*/  }, 1000)

현재 contextAPI 로 만든 state 에 접근하기 위해서 무조건  action 으로 처리해야 되기 때문에,

 

 

 

    useEffect( ()=>{
        const timer = setInterval( ()=>{
            dispatch({ type:INCREMENT_TIMER });
        }, 1000 );

        return()=>{
            clearInterval(timer)
        }
    } )

 액션으로 만들어준 다음

        case INCREMENT_TIMER:{
            return{
                ...state,
                timer: state.timer+1,
            }
        }

reducer 에서 처리해야된다

 

 

버튼눌렀을때 타이머가 실행되야 함으로  gameOver state를 이용해서 타이머를 제어한다

    useEffect( ()=>{
        let timer ; 
        if( gameOver == false ){
            timer = setInterval( ()=>{
                dispatch({ type:INCREMENT_TIMER });
            }, 1000 );
        }
        return()=>{
            clearInterval(timer)
        }
    } ,[gameOver])

 

 

 

 

 

Mine.jsx

import React ,{ useEffect, useReducer, createContext, useMemo }from 'react';
import Form from './Form'; 
import Table from './Table';


export const TD_STATE = {
    MINE:-7,  //지뢰심어짐
    NORMAL:-1, //기본
    FLAG: -3, //우클릭1 깃발
    QUESTION:-2, //우클릭2 물음표
    QUESTION_BUT: -4,  //우클릭2 물음표(근데.지뢰있음)
    FLAG_BUT:-5, //우클릭1 깃발(근데.지뢰있음)
    OPENED_MINE: -6, //지뢰를클릭함.
    OPENED :0, // 통과.
}

// 입력받은 값으로 칸 만들고 지뢰도 심어주는 함수.
const plantMine = (row, col, mine)=>{ 
    const numArray = Array(row*col).fill().map((arr,i)=>{ return i }); //배열 0~99 
    
    const shuffle=[];
    while( numArray.length > ((row*col)-mine) ){ // 100>80 // 99-- > 80 

        const beMine = numArray.splice(Math.floor(Math.random()*numArray.length), 1)[0]; 
        //20개의 랜덤숫자(지뢰 위치가 될)

        shuffle.push(beMine);
    }

    //칸만들어서 전부다 기본으로 채운다
    const data = [];
    for (let i=0; i<row; i++){
        const rowData = [];
        for(let j=0; j<col; j++){
            rowData.push(TD_STATE.NORMAL);
        }
        data.push(rowData);
    }

    for( let k=0; k<shuffle.length; k++){ //지뢰갯수만큼.

        const ver = Math.floor(shuffle[k] / col); //몇번째줄
        const hor = shuffle[k] % col; //몇번째칸
        data[ver][hor]=TD_STATE.MINE; //지뢰를 심음
    }
     
    return data;
}




export const TableContext = createContext({  
    tableData:[],
    dispatch: () => {},
    gameOver: true,
});

const initialState = { //2
    tableData:[],
    timer:0,
    result:'',
    gameOver: true, 
    opendCount: 0,
    tableInfo:{ row:0, col:0, mine:0},  //////
}
export const START_GAME = 'START_GAME';
export const OPEN_TD = 'OPEN_TD';
export const OPEN_MINE = 'OPEN_MINE';
export const SET_QUESTION = 'SET_QUESTION';
export const SET_NORMAL = 'SET_NORMAL';
export const SET_FLAG = 'SET_FLAG';
export const INCREMENT_TIMER = 'INCREMENT_TIMER';

const reducer = (state,action)=>{ //3
    switch (action.type){
        case START_GAME:
            return{
                ...state,
                tableData: plantMine(action.row, action.col, action.mine),
                gameOver: false,
                opendCount:0,
                tableInfo: { row: action.row, col: action.col, mine: action.mine},
                result:'',
                timer:0,
            };
        case OPEN_TD:{
            const tableData = [...state.tableData];
            tableData.forEach( (tr, i) =>{
                tableData[i] = [...state.tableData[i]];    //  ? 
                //tableData[i] = [...tr];
            }); 
            const checked = [];
       
            let tdOpen = 0;  ////// 
            const checkAndOpen=(tr, td)=>{ 
               
                if( tr<0 || tr >= tableData.length || td<0 || td >= tableData[0].length ){
                    return; 
                }  
                if( [TD_STATE.OPENED, TD_STATE.QUESTION, TD_STATE.QUESTION_BUT, TD_STATE.FLAG, TD_STATE.FLAG_BUT].includes( tableData[tr][td]) ){
                    return;
                }
                
                if( checked.includes(tr+','+td) ){ 
                    return;
                }else{
                    checked.push(tr+','+td);
                }  
                //주변 지뢰있는지검사.
                let around = [
                    tableData[tr][td-1], tableData[tr][td+1]   
                ];
                if(tableData[tr-1]){   
                    around = around.concat(
                        tableData[tr-1][td-1],
                        tableData[tr-1][td],
                        tableData[tr-1][td+1],
                    )
                } 
                if(tableData[tr+1]){   
                    around =around.concat(
                        tableData[tr+1][td-1],
                        tableData[tr+1][td],
                        tableData[tr+1][td+1],
                    );
                } 
                const mineCount = around.filter((v)=>( [TD_STATE.MINE, TD_STATE.FLAG_BUT, TD_STATE.QUESTION_BUT].includes(v) )).length; 
                //지뢰가없으면 주변까지 연다.
                if(mineCount === 0){   
                    const near = [];
                    if( tr-1 > -1 ){  
                        near.push([tr-1, td-1]);  
                        near.push([tr-1, td]);    
                        near.push([tr-1, td+1]);  
                    }
                    near.push([tr, td-1]);
                    near.push([tr, td+1]);
                    if( tr+1 < tableData.length ){  
                        near.push([tr+1, td-1]);
                        near.push([tr+1, td]);
                        near.push([tr+1, td+1]);   
                    } 
                    near.forEach((n)=>{
                        if( tableData[n[0]][n[1]] !== TD_STATE.OPEND ){
                            
                            checkAndOpen(n[0],n[1]);
                        }
                    })  
                } 
                //지뢰 있으면 하나만 연다
               
                if( tableData[tr][td] === TD_STATE.NORMAL ){
                    tdOpen ++;  
                    tableData[tr][td] = mineCount;   /////////
                }
                
                
                
            } 
            checkAndOpen( action.trIndex, action.tdIndex ); 
            let gameOver = false;
            let result = '';
            

            //승리조건 체크
            
             
            console.log(  `((${state.tableInfo.row}*${state.tableInfo.col})-${state.tableInfo.mine}) === ${state.opendCount}+${tdOpen}` );
            
            if( ((state.tableInfo.row*state.tableInfo.col)-state.tableInfo.mine) === state.opendCount+tdOpen ){
                gameOver=true;
                result=`${state.timer}초만에 승리`;
            }
              
            //console.log('opendCount:'+state.opendCount); 
 
            return{
                ...state,
                tableData,
                gameOver,
                opendCount: state.opendCount + tdOpen,
                result, 
            }
        }   
        case OPEN_MINE:{
            const tableData = [...state.tableData];
            tableData[action.trIndex] = [...state.tableData[action.trIndex]];
            tableData[action.trIndex][action.tdIndex] = TD_STATE.OPENED_MINE;
            return{
                ...state,
                tableData,
                gameOver:true,
            }
        }
        case SET_QUESTION:{
            const tableData = [...state.tableData];
            tableData[action.trIndex] = [...state.tableData[action.trIndex]];
            if( tableData[action.trIndex][action.tdIndex] === TD_STATE.MINE){ 
                tableData[action.trIndex][action.tdIndex] = TD_STATE.QUESTION_BUT; 
            }else{
                tableData[action.trIndex][action.tdIndex] = TD_STATE.QUESTION; 
            }
            return{
                ...state,
                tableData,
            }
        }
        case SET_NORMAL:{
            const tableData = [...state.tableData];
            tableData[action.trIndex] = [...state.tableData[action.trIndex]];
            if( tableData[action.trIndex][action.tdIndex] === TD_STATE.QUESTION_BUT){ 
                tableData[action.trIndex][action.tdIndex] = TD_STATE.MINE; 
            }else{
                tableData[action.trIndex][action.tdIndex] = TD_STATE.NORMAL; 
            }
            return{
                ...state,
                tableData,
            }
        }
        case SET_FLAG:{
            const tableData = [...state.tableData];
            tableData[action.trIndex] = [...state.tableData[action.trIndex]];
            if( tableData[action.trIndex][action.tdIndex] === TD_STATE.MINE){ 
                tableData[action.trIndex][action.tdIndex] = TD_STATE.FLAG_BUT; 
            }else{
                tableData[action.trIndex][action.tdIndex] = TD_STATE.FLAG; 
            }
            return{
                ...state,
                tableData,
            }
        } 
        case INCREMENT_TIMER:{
            return{
                ...state,
                timer: state.timer+1,
            }
        }

        default:
            return state;
    }
}

const Mine = () =>{

    const [state, dispatch] = useReducer( reducer, initialState); //1
    const { timer , result , tableData, gameOver } = state;
 
    const tblContextValue = useMemo( ()=>({ tableData, dispatch , gameOver }) ,[tableData]);

    useEffect( ()=>{
        let timer ; 
        if( gameOver == false ){
            timer = setInterval( ()=>{
                dispatch({ type:INCREMENT_TIMER });
            }, 1000 );
        }
        return()=>{
            clearInterval(timer)
        }
    } ,[gameOver])

    return(
        <TableContext.Provider value={tblContextValue}>
            <h3>지뢰찾기</h3>
            <Form />
            <Table />
            <p>타이머:{timer}</p>
            <p>결과:{result}</p>
        </TableContext.Provider>
    )
}

export default Mine;

 

Form.jsx

import React ,{ useState, useCallback ,useContext} from 'react';
import { TableContext ,START_GAME } from './Mine'; //

const Form = () =>{

    const [ row, setRow ] = useState(10);
    const [ col, setCol] = useState(10);
    const [ mine, setMine] = useState(20);    
    const { dispatch } = useContext(TableContext);  
      
    const onChangeRow = useCallback( (e) =>{
       setRow(e.target.value); 
    });
    
    const onChangeCol = useCallback((e) =>{
        setCol(e.target.value); 
    });
     
    const onChangeMine = useCallback((e) =>{
        setMine(e.target.value); 
    });

    const onClickButton = useCallback( () =>{
        dispatch({ type:START_GAME, row, col, mine });
    }, [row, col, mine]);

    return(
        <>
            <input type="number" placeholder="가로칸 갯수" value={ row} onChange={onChangeRow} />
            <input type="number" placeholder="세로칸 갯수" value={ col} onChange={onChangeCol} />
            <input type="number" placeholder="지뢰 갯수" value={ mine} onChange={onChangeMine} />
            <p><button onClick={onClickButton}>게임시작</button></p>
        </>
    );
}


export default Form;

Table.jsx

import React, { useContext } from 'react';
import Tr from './Tr';
import { TableContext } from './Mine';

const Table = () =>{
    const { tableData } = useContext(TableContext);
    return(
        <table>
            <tbody>
            {Array(tableData.length).fill().map( (tr, i)=>(<Tr trIndex={i}/>) )}
            </tbody>
        </table>
    );
}


export default Table;

Tr.jsx

import React, { useContext } from 'react';
import Td from './Td';
import { TableContext } from './Mine';


const Tr = ({trIndex}) =>{
    const { tableData }= useContext(TableContext);
    //console.log( tableData[0]);
    return(
        <tr>
            { tableData[0] && Array(tableData[0].length).fill()
            .map( ( td, i )=><Td trIndex={trIndex} tdIndex={i}/> ) }
        </tr>
    );
}


export default Tr;

Td.jsx

import React ,{ useContext, useCallback }from 'react';
import { TableContext, TD_STATE, OPEN_TD , OPEN_MINE, SET_QUESTION, SET_NORMAL, SET_FLAG} from './Mine';


const tdStyle = ( stateCode ) =>{
    switch( stateCode ){
        case TD_STATE.NORMAL:
        case TD_STATE.MINE:
            return{ background:'#6b6b6b'}
        case TD_STATE.OPENED:
            return{ background: '#fff' } 
        case TD_STATE.OPENED_MINE: 
            return{ background:'darkRed'}
        case TD_STATE.FLAG_BUT:
        case TD_STATE.FLAG:
            return{ background:'gold'}
        case TD_STATE.QUESTION:
            return{ background:'#af97e8'}
    } 
};

const tdText = ( stateCode ) =>{ 
    switch(stateCode){
        case TD_STATE.NORMAL:
            return '';
        case TD_STATE.MINE:
            return 'X';
        case TD_STATE.OPENED_MINE:
            return '펑!';
        case TD_STATE.FLAG_BUT:
        case TD_STATE.FLAG:
            return '!~'
        case TD_STATE.QUESTION:
            return '?'
        default: 
            return stateCode || '';
    }

}



const Td = ({trIndex, tdIndex}) =>{
    const{ tableData , dispatch , gameOver } = useContext(TableContext);
    const stateCode = tableData[trIndex][tdIndex];  //-1, -7

    const onClickTd = useCallback(() =>{ 
        if(gameOver){
            return;
        }

        switch( tableData[trIndex][tdIndex] ){ //0, -1 ,-7 ... 

            case TD_STATE.NORMAL :  //그냥칸이면(-1) -열리게
            
                dispatch({ type:OPEN_TD, trIndex, tdIndex });
                return;
            case TD_STATE.MINE :  //마인칸이면(-7) -open_mine액션
                dispatch({ type:OPEN_MINE, trIndex, tdIndex });
                console.log('왜')
                return;
            default : //이미열린칸, 깃발, 깃발-지뢰, 물음표, 물음표-지뢰 는-동작안하게
                return;

        }
        
    },[ tableData[trIndex][tdIndex] , gameOver ]); //계속바뀌는배열이니까 넣어주는것!

    const onClick_RightTd =(e)=>{
        e.preventDefault();
        if(gameOver){
            return;
        }
        switch( tableData[trIndex][tdIndex]  ){

            case TD_STATE.NORMAL :
            case TD_STATE.MINE :  // 지뢰, 기본칸이면 깃발로만들어줌.
                dispatch({ type:SET_FLAG, trIndex, tdIndex }); 
                return;
            case TD_STATE.FLAG_BUT:
            case TD_STATE.FLAG:
                dispatch({ type:SET_QUESTION, trIndex, tdIndex }); 
                return;
            case TD_STATE.QUESTION_BUT:
            case TD_STATE.QUESTION:
                dispatch({ type:SET_NORMAL, trIndex, tdIndex }); 
                return;
            default : 
                return;
        }
    }


    return(
        <td onClick={onClickTd} onContextMenu={onClick_RightTd} style={tdStyle(stateCode)}>
            {tdText(stateCode)}
        </td>
    );
}
 
export default Td;

'React > basic' 카테고리의 다른 글

14. (4)context API  (0) 2020.07.31
14 (2)context API  (0) 2020.07.29
14. context API  (0) 2020.07.29
13. (3) useReducer  (0) 2020.07.29
13. (2) useReducer  (0) 2020.07.29