개발노트

13. (3) useReducer 본문

React/basic

13. (3) useReducer

aloha2jh 2020. 7. 29. 01:59

추가할 기능

-한번 클릭했던 칸 다시 클릭 못하게,

-승자 판단, 무승부 판단

 

 

한번 클릭했던 칸 다시 클릭 못하게 하기

 

클릭하면 ''에서 'o'로 data 들어가게 되니까

const onClickTd = useCallback(() =>{
   if( tdData ){
   		return;
   }
   ...
,[tdData]);

(1)클릭했을때 tdData 값 있으면 그냥 리턴 시켜버리면 된다.

 

useCallback으로 감싸서 함수를 기억하고 있는데, 함수말고도; 너무잘 기억해서 다른 값들을 업데이트 안하니까,

바뀌는값이있을경우 꼭 의존성 배열에 추가해주어야 한다.

(2)tdData 바뀌는 값이니까 의존성배열에 추가해줌

 

  

dispatch 에서 state바뀔때 비동기 <-> 리덕스는 동기

dispatch({type: CHANGE_TURN}); //o 에서 x로 바꿔줘!
// turn값은 o로 나온다 비동기이기때문에

 

비동기에 따라서 처리할때는 uesEffect를 써야한다..

 

 

클릭할때마다

useEffect(()=>{  /*할일*/  } , [ something ])

의존성 배열에 tableData를 넣고, 그럼 tableData가바뀔때-(클릭할때마다) 마다 useEffect가 실행되니깐.

그때마다 할일을 적어준다.

 

 

승자 판단

효율적으로 체크하기 위해 클릭한 값에 해당되는 가로 세로 대각선만 검사한다.

그럴려면 클릭한 값을 저장해야 되서,

클릭 이벤트 와 함께 CLICK_TD액션이 일어나고 그걸 처리하는 reducer에서

        case CLICK_TD :  {   
          
            const tableData = [...state.tableData ];  
            tableData[action.tr] = [...tableData[action.tr]];   
            tableData[action.tr][action.td] = state.turn;
            return{
                ...state,
                tableData: tableData,
                recentTd : [ action.tr, action.td]  ////
            }
        }
        
        
       
const initialState = {
    recentTd:[-1,-1]
}

이렇게 추가해줌.

 

 

그리고 판단해주는 useEffect

 useEffect( ()=>{
        const [ tr, td ] = recentTd;
        if( tr < 0 ){
            return;
        }
        //가로줄검사
        if (tableData[row][0] === turn && tableData[row][1] === turn && tableData[row][2] === turn) {
            win = true;
          }
        //세로줄검사
          if (tableData[0][cell] === turn && tableData[1][cell] === turn && tableData[2][cell] === turn) {
            win = true;
          }
        //대각선1
          if (tableData[0][0] === turn && tableData[1][1] === turn && tableData[2][2] === turn) {
            win = true;
          }
        //대각선2
          if (tableData[0][2] === turn && tableData[1][1] === turn && tableData[2][0] === turn) {
            win = true;
          }

          if(win){
            dispatch({ type:SET_WINNER, winner: turn});
          }else{

          }
    },[recentTd]);

 

Tr컴포넌트에서 

dispatch({type:CLICK_TD, tr: trIndex, td: tdIndex }); //칸클릭, (o,x)의 표시
dispatch({type: CHANGE_TURN}); //o면x, x면o로 변경.

비동기문제의 발생.

현재클릭한애의 빙고체크 &현재trun인 사람이 WIN으로 useEffect 로직이 처리되있으나

클릭한애의 빙고체크 하고 있을때 다음턴으로 넘어가짐

 

그래서.. 코드변경 하면 된다

이긴거 아닐때, 다음턴으로 넘기기

  if(win){
            dispatch({ type:SET_WINNER, winner: turn});
          }else{ 
            dispatch({type: CHANGE_TURN}); //o면x, x면o로 변경.
          }

 

무승부 검사

테이블에 데이터가 다 들어 있는지 검사하면 된다.

          if(win){
            dispatch({ type:SET_WINNER, winner: turn});
          }else{ 

            let all = true; //무승부가true인상태
            //무승부검사
            tableData.forEach( (tr)=>{
                row.forEach( (td)=>{
                    if(!td){ //td하나라도 안차있으면 무승부false
                        all=true;
                    }
                })
            });

            if(all){
               
            }else{
                dispatch({type: CHANGE_TURN}); //o면x, x면o로 변경.
            }


          }

 

 

(결판났으니) 게임 리셋

        case RESET_GAME :{
            return{
                ...state,
                turn :'o',
                tableData: [ ['','',''],['','',''],['','',''] ],
                recentTd:[-1,-1] 
            }
        }

reducer로 게임리셋액션 처리를 만들었고,

 

승리일때, 무승부일때 -게임리셋하도록 액션실행 하면된다.

 

 

 

성능최적화

리셋과, 승리표시도 잘해주지만

빨리클릭할경우 렌더링 계속 일어나서

성능최적화를 해야 된다

(한칸만 클릭했는데 전체가 렌더링 되서 문제)

import React ,{ useCallback } from 'react';
import { CLICK_TD  } from './Tictactoe';

const Td = ({trIndex, tdIndex, dispatch ,tdData})=>{  
    console.log('td렌더링'); ///
    const onClickTd = useCallback(() =>{
        console.log( trIndex, tdIndex);
        if( tdData ){
            return;
        }
        dispatch({type:CLICK_TD, tr: trIndex, td: tdIndex }); //칸클릭, (o,x)의 표시
    },[tdData]);
    return(
    <td onClick={onClickTd} >{tdData}</td>
    )
};
export default Td;

지금.. 9번 렌더링 이러나는것도 모자라 9*2로 일어나고 있음.

 

문제를 파악하기 위해

const Td = ({ propsA })=>{  
    const ref = useRef([]);
    useEffect( ()=>{
        console.log( propsA === ref.current[0])
        ref.current = [ propsA/* props */]
    },[  propsA/* props들  */])
...
}

이런식으로 useRef를 사용해

(A) propsA 의값을 저장하고 => ref.current[0] 여기에저장됨

만약 propsA의 값이 바뀌면

A에서 저장했던 이전값ref.current[0]과 현재값propsA인 가 false가 나오게 됨.

 

 

const Td = ({trIndex, tdIndex, dispatch ,tdData})=>{  
   
    const ref = useRef([]);
    useEffect( ()=>{
        console.log( trIndex === ref.current[0], tdIndex === ref.current[1], 
            dispatch === ref.current[2], tdData === ref.current[3]);

        ref.current = [ trIndex, tdIndex, dispatch ,tdData]
    },[  trIndex, tdIndex, dispatch ,tdData ])
}

이런식으로 props로 받은 값들중 어떤 값이 바뀌어서 리렌더링 되고있는건지 알아내도록 한다

 

 

지금 Td컴포넌트여기서는, tdData가 바뀌어서(''->O) 리렌더링 되고 있는것이고, (원하는값만 잘바뀌고 있는상태)

그래서 부모컴포넌트Tr컴포넌트에서 props를 넘겨줄때(넘겨서 값을변경할때)

반복문을 써서 자식컴포넌트가 전부다 리렌더링 되고 있는 문제 ..가되는듯 하여

Tr컴포넌트의 자식인 Td컴포넌트에 React.memo를 써준다

이렇게 td렌더링이 한번만 일어나게 된다.

 

 

 

마찬가지로, Table컴포넌트에서 반복문으로 생성하는 Tr컴포넌트도 렌더링이 필요없이많이 발생하는걸 확인할 수 있는데, 똑같이 memo컴포넌트로 감싸준다.

 

import React , { useState ,  useReducer , useCallback, useEffect } from 'react';
import Table from './Table'; 
 
const initialState = {
    winner: '',
    turn :'o',
    tableData: [ ['','',''],['','',''],['','',''] ],
    recentTd:[-1,-1]
}

export const SET_WINNER = 'SET_WINNER';
export const CLICK_TD = 'CLICK_TD';
export const CHANGE_TURN = 'CHANGE_TURN';
export const RESET_GAME = 'RESET_GAME';

 
const reducer = (state, action) =>{
    switch( action.type ){
        case SET_WINNER :
            return{
                ...state,
                winner:action.winner
            }
        case CLICK_TD :  {   
          
            const tableData = [...state.tableData ];  
            tableData[action.tr] = [...tableData[action.tr]];   
            tableData[action.tr][action.td] = state.turn;
            return{
                ...state,
                tableData: tableData,
                recentTd : [ action.tr, action.td]
            }
        }
        case CHANGE_TURN : {
            return{
                ...state,
                turn: state.turn === 'o'?'x':'o'
            }
        }
        case RESET_GAME :{
            return{
                ...state,
                turn :'o',
                tableData: [ ['','',''],['','',''],['','',''] ],
                recentTd:[-1,-1] 
            }
        }
        default:
            return state;
    }
}

const Tictactoe = () =>{ 
    const [state, dispatch] = useReducer( reducer, initialState );
    const { recentTd, tableData, winner, turn } = state;
    const onClickTable = useCallback ( ()=>{ 

    },[]);

    useEffect( ()=>{
        const [ tr, td ] = recentTd;
        if( tr < 0 ){
            return;
        }
        let win = false; 
        if (tableData[tr][0] === turn && tableData[tr][1] === turn && tableData[tr][2] === turn) {
            win = true;
          } 
          if (tableData[0][td] === turn && tableData[1][td] === turn && tableData[2][td] === turn) {
            win = true;
          } 
          if (tableData[0][0] === turn && tableData[1][1] === turn && tableData[2][2] === turn) {
            win = true;
          } 
          if (tableData[0][2] === turn && tableData[1][1] === turn && tableData[2][0] === turn) {
            win = true;
          }

          if(win){
            dispatch({ type:SET_WINNER, winner: turn});
            dispatch({type:RESET_GAME, winner: turn });  
          }else{ 

            let all = true; //무승부가true 
            tableData.forEach( (tr)=>{
                tr.forEach( (td)=>{
                    if(!td){  
                        all=false;
                    }
                })
            });

            if(all){
               dispatch({type:RESET_GAME, winner: turn });  
            }else{ 
                dispatch({type: CHANGE_TURN});  
            } 

          }
    },[recentTd]);
    
    return (
        <>
            <Table onClickTableEvent={onClickTable} 
                    tableData={tableData} 
                    dispatch={dispatch}
            />
            {winner && <p>{winner} 님의 승리</p> }
        </>
    ) 
}
 
export default Tictactoe;
import React from 'react';
import Tr from './Tr';

const Table = ( {onClickTableEvent, tableData ,dispatch} )=>{
    return(
        <> 
        <table >
            <tbody>
            { Array(tableData.length).fill().map( ( tr ,i )=>( <Tr trIndex={i} trData={ tableData[i] } dispatch={dispatch}/> ) ) }
            </tbody>
        </table></>
    )
}

export default Table;
import React ,{ memo} from 'react';
import Td from './Td';

const Tr = memo(({trData ,trIndex, dispatch}) =>{
    console.log('tr렌더링');
    return(
        <tr> 
        { Array(trData.length).fill().map(( td ,i )=>( 
        <Td trIndex={trIndex} tdIndex={i} 
            dispatch={dispatch} 
            tdData = {trData[i]}
        /> ) )}
        </tr>
    )
});

export default Tr;
import React ,{ useCallback, useEffect, useRef ,memo} from 'react';
 
import { CLICK_TD  } from './Tictactoe';

const Td = memo( ({trIndex, tdIndex, dispatch ,tdData})=>{  
    //console.log('td렌더링'); 
    
    const ref = useRef([]);
    useEffect( ()=>{

        //console.log( trIndex === ref.current[0], tdIndex === ref.current[1], 
       //     dispatch === ref.current[2], tdData === ref.current[3]);
        
        //console.log( "ref.current[3]:"+ref.current[3] );
        //console.log( "tdData:"+tdData );
        ref.current = [ trIndex, tdIndex, dispatch ,tdData]
    },[  trIndex, tdIndex, dispatch ,tdData ])

    const onClickTd = useCallback(() =>{
        console.log( trIndex, tdIndex);

        if( tdData ){
            return;
        }

        dispatch({type:CLICK_TD, tr: trIndex, td: tdIndex }); //칸클릭, (o,x)의 표시
    },[tdData]);

    return(
        <td onClick={onClickTd} >{tdData}</td>
    )
});

export default Td;

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

14 (2)context API  (0) 2020.07.29
14. context API  (0) 2020.07.29
13. (2) useReducer  (0) 2020.07.29
13. useReducer  (0) 2020.07.28
12. hooks / useEffect , useMemo  (0) 2020.07.28