개발노트

14. context API 본문

React/basic

14. context API

aloha2jh 2020. 7. 29. 20:17

useReducer, contextAPI 를 사용해서 지뢰찾기 게임 만들기

Mine Form Table Tr Td 컴포넌트

 

Mine컴포넌트

(1)useReducer 만들고

(2)초기값들  tableData선언

(3)reducer함수만들고, 

import React ,{  useReducer, createContext }from 'react';
import Form from './Form'; 
 
const initialState = { //(2)
    tableData:[],
    timer:0,
    result:0,
}

const reducer = (state, action)=>{  //(3)
    switch (action.type){

        default:
            return state;
    }
}

const Mine = () =>{

    const [state, dispatch ] = useReducer( reducer,initialState);  //(1)
    const { timer , result } = state;

    return(
        <>
            <h3>지뢰찾기</h3>
            <Form />
            <p>타이머:{timer}</p>
            <p>결과:{result}</p>
        </>
    )
}

export default Mine;

 

 

 

Form컴포넌트

화면 몇 by 몇, 지뢰갯수, 타이머 값을 받을 Form컴포넌트를 만든다

(1)useState로 state이름, setState를만들고,

(2)react는input바뀔때 값 직접바꿔줘야 되니까  각각 onChange이벤트걸어서 e.target.value로 set해준다.

 

그리고 게임시작 버튼을 만든다

(여기 게임시작버튼에 context API적용예정)

import React ,{useState, useCallback} from 'react';

const Form = () =>{

    const [ row, setRow ] = useState( 10);  //(1)
    const [ col, setCol] = useState(10);
    const [ mine, setMine] = useState(20);

    const onChangeRow = useCallback( (e) =>{ //(2)
       setRow(e.target.value); 
    });
    
    const onChangeCol = useCallback((e) =>{
        setCol(e.target.value); 
    });
     
    const onChangeMine = useCallback((e) =>{
        setMine(e.target.value); 
    });

    const onClickButton = () =>{
        
    }

    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} />
            <button onClick={onClickButton}>게임시작</button>
        </>
    );
}


export default Form;

 

 

context API - createContext (Mine component)

(1) React.createContext를 불러와서 createContext()함수호출해준다.

-초기값을 넣어줌

-이 context를 다른파일에 쓸수있게 export 해줌!

 

(2)JSX안에서 context 에 접근하고 싶은 컴포넌트들을 context.provider 로 묶어준다

(3) TblContextValue로 넘겨주고싶은 값들을 넣어줌

 

 

 

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

//const TableContext = createContext(); //(1)

export const TableContext = createContext({  //(1)
    tableData:[],
    dispatch:()=>{}
});
...

const Mine = () =>{

    const [state, dispatch ] = useReducer( reducer,initialState);
    const { timer , result , tableData } = state;

    return(
        <TableContext.Provider TblContextValue={{ tableData: tableData, dispatch}} > <!-- (2)(3) -->
            <h3>지뢰찾기</h3>
            <Form />
            <p>타이머:{timer}</p>
            <p>결과:{result}</p>
        </TableContext.Provider>
    )
}

export default Mine;

 

context API - useContext  (Form component)

React.useContext를 사용해서 export한 TableContext 를 받아온다

//Form.jsx
const tblContextValue = useContext(TableContext); 

 

TableContext(Context)를 받아왔으니, TblContextValue 값으로 넣어준 dispatch에 접근이 가능

//Mine.jsx
<TableContext.Provider tblContextValue={{ tableData: tableData, dispatch}} > 
</TableContext.Provider>

 

//Form.jsx
const tblContextValue = useContext(TableContext); 
const dispatch = tblContextValue.dispatch;  // 이렇게 dispatch에 접근이 가능

이걸 비구조화할당으로 

const { dispatch } = useContext(TableContext);  

 

 

 

그리고 TableContext.Provider 에서 전달하는 값들이

함수컴포넌트가 렌더링될때 매번 tblContextValue객체들이 생기지 않게 (만들지 않게)

 

useMemo를 써서 보낸다

    const tblContextValue = useMemo( ()=>({ tableData, dispatch },[tableData]) );

    return(
        <TableContext.Provider value={tblContextValue} > //꼭 value로 넘겨줘야 함...
        ...

 

 

 

Form컴포넌트에서 액션을 dispatch시켜서 입력받은 칸갯수와 지뢰갯수를 보내고,

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

 

Mine 컴포넌트에서 reducer로 action.row ... 로 값들을 얻어와 액션을 처리

export const START_GAME = 'START_GAME';
const reducer = ()=>{
    switch (action.type){
        case START_GAME:
            return{
                ...state,
                tableData: plantMine(action.row, action.col, action.mine)
            };
...
}

 

 

 

2차원 배열을 지뢰 넣어서 만들기

칸마다 상태코드가 있음. 기본 -1 ,지뢰는 -7

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

숫자로 표시하면 헷갈리니까 상태코드로 만들어서

 

// 입력받은 값으로 칸 만들고 지뢰도 심어주는 함수.
const plantMine = (row, col, mine)=>{
    console.log(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] / cell); //몇번째줄
        const hor = shuffle[k] % col; //몇번째칸
        data[ver][hor]=TD_STATE.MINE; //지뢰를 심음
    }
    return data;
}

(0) Array(100) 0~99배열만들고,

while반복문으로 100-- > (100-지뢰갯수) 돌리면서, 랜덤 20개 숫자 구함

(1)입력받은 값으로 2차원 배열 만들고,  [ -1,-1,-1,-1,-1, ], [ -1,-1,-1,-1,-1, ], [ -1,-1,-1,-1,-1, ] ...

(2)지뢰갯수에맞게 지뢰도 랜덤으로 심어준다  [ -1,-1,-7,-1,-1, ], [ -1,-7,-1,-7,-1, ], [ -1,-1,-1,-1,-1, ] ...

 

 

 

화면그리기

Table, Tr, Td 컴포넌트로 context API를 사용해서 화면을 그린다 

Table -> Tr, Tr -> Td 이런식으로 props로 물려 물려 주지않고

 

(1)Table , Tr 컴포넌트 둘다 useContext를 이용하여 createContext로 만든TableContext를 가져온다

거기에 있는 tableData 를 분해구조할당으로 가져옴!

(2) tableData 를 이용해 JSX 를 사용해 화면 그린다

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

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


export default Table;

 

마찬가지로 Tr컴포넌트도 useContext로 tableData 를 사용

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;

 

 

 

 

Table에서 Tr에게 props로  tr index 넘겨주고,

Tr에서 Td에게 받은 tr inde 랑, td index 넘겨줘서

Td는 [trIndex][tdIndex]로, Context의 tableData 값대로 화면에 값을 보여준다

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

const Td = ({trIndex, tdIndex}) =>{
    const{ tableData } = useContext(TableContext);
    return(
    <td>{tableData[trIndex][tdIndex]}</td>
    );
}
 
export default Td;

 

 

번호상태값 받아서 스타일, 텍스트 바꾸기

상태코드stateCode -1,-7를 받아서

style적용과 어떤text출력할지 만들어주는 함수를 만든다

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

const tdStyle = ( stateCode ) =>{

};

const tdText = ( stateCode ) =>{

}


const Td = ({trIndex, tdIndex}) =>{
    const{ tableData } = useContext(TableContext);
    const stateCode = tableData[trIndex][tdIndex];  //-1, -7
    return(
        <td style={tdStyle(stateCode)}>
            {tdText(stateCode)}
        </td>
    );
}
 
export default Td;

 

 

원하는 배경색상, 텍스트를 표시를 지정

Mine컴포넌트에서 export한 TD_STATE를 가져와서 사용

TD_STATE.NORMAL이면, 즉 -1이면

const tdStyle = ( stateCode ) =>{
    switch( stateCode ){
        case TD_STATE.NORMAL:
        case TD_STATE.MINE:
            return{
                background:'#6b6b6b'
            }
        case TD_STATE.OPENED:
            return{
                background: '#fff'
            } 
    } 
};

const tdText = ( stateCode ) =>{
    switch(stateCode){
        case TD_STATE.NORMAL:
            return '';
        case TD_STATE.MINE:
            return 'X';
        default:
            return '';
    }

}

 

// 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 깃발(근데.지뢰있음)
    CLICKED_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: () => {},
});

const initialState = { //2
    tableData:[],
    timer:0,
    result:0,
}
export const START_GAME = 'START_GAME';
const reducer = (state,action)=>{ //3
    switch (action.type){
        case START_GAME:
            return{
                ...state,
                tableData: plantMine(action.row, action.col, action.mine)
            };
        default:
            return state;
    }
}

const Mine = () =>{

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

    //const tblContextValue = useMemo( ()=>{ tableData: tableData, dispatch } ,[tableData]);
    const tblContextValue = useMemo( ()=>({ tableData, dispatch }) ,[tableData]);

    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}from 'react';
import {TableContext, TD_STATE } 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'
            } 
    } 
};

const tdText = ( stateCode ) =>{
    switch(stateCode){
        case TD_STATE.NORMAL:
            return '';
        case TD_STATE.MINE:
            return 'X';
        default:
            return '';
    }

}


const Td = ({trIndex, tdIndex}) =>{
    const{ tableData } = useContext(TableContext);
    const stateCode = tableData[trIndex][tdIndex];  //-1, -7
    return(
        <td style={tdStyle(stateCode)}>
            {tdText(stateCode)}
        </td>
    );
}
 
export default Td;

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

14 (3)context API  (0) 2020.07.30
14 (2)context API  (0) 2020.07.29
13. (3) useReducer  (0) 2020.07.29
13. (2) useReducer  (0) 2020.07.29
13. useReducer  (0) 2020.07.28