개발노트

12. hooks / useEffect , useMemo 본문

React/basic

12. hooks / useEffect , useMemo

aloha2jh 2020. 7. 28. 00:46

useState, useRef 로 변경

import React, { useState , useRef , useEffect} from 'react';
import Ball from './Ball';

function getPickNumbers(){
    console.log('pickNum START');
    const numArray = Array(45).fill().map( (v,i) => i + 1 );   
    const shuffle = [];
    while( numArray.length>0 ){    
        shuffle.push( numArray.splice(    Math.floor( Math.random() * numArray.length )   ,1)[0]  ); 
    }
    const bonusNumber = shuffle[shuffle.length -1]; 
    const rltNumbers = shuffle.slice(0,6).sort( (p,c)=>p-c );  
    return[...rltNumbers, bonusNumber];  
};

const Lotto = () =>{

    const [pickNumbers, setPickNumbers] = useState(getPickNumbers());
    const [balls, setBalls] = useState([]);
    const [bonus, setBonus] = useState(null);
    const [rePick, setRePick] = useState(false);
    const timer = useRef([]);

    // const lottoStarting = ()=>{ 
    //     for( let i=0; i<pickNumbers.length-1; i++){ 
    //         timer.current[i] = setTimeout( ()=>{   
    //             setBalls( (prevBalls)=>{ return [...prevBalls, pickNumbers[i]] }  ) 
    //         } , (i+1)*100 ) ;
    //     }
    //     setTimeout(
    //         ()=>{
    //             setBonus(pickNumbers[6]);
    //             setRePick(true);
    //         } 
    //         ,700
    //     )
    // } 
    
    const onClickRePick = () =>{
        //초기화
        setPickNumbers(getPickNumbers());
        setBalls([]);
        setBonus(null);
        setRePick(false);
        timer.current=[];
    } 
 

    // componentDidMount(){
    //     this.lottoStarting ();
    // }
    
    // componentWillUnmount(){
    //     this.timer.forEach( (v)=>{ clearTimeout(v) });
    // } 
    // componentDidUpdate( prevProps, prevState){
    //     if( this.state.balls.length === 0 ){
    //         this.lottoStarting ();
    //     }
    // }
 
    return(
        <>
            <h1>당첨숫자</h1>
            <ul id="rlt_box">
                {  balls.map( ( v )=>( <Ball key={v} num={v} /> ) )} 
                    
                { bonus &&  <Ball num={bonus} />  }
            </ul>  
            { rePick && <button onClick={ rePick ? onClickRePick :()=>{} } >한번더</button> }
        </>
    ) 
}
export default Lotto;

 

 

componentDidMount, componentwillUnmout, componentDidUpdate를

useEffect 로 바꾸기

 

▶use Effect기본 설명

useEffect(()=>{} , [])

이렇게 의존성배열이 [] 비어있으면 componentDidMount와 동일한 기능

 

useEffect(()=>{} , [ something ])

배열에 요소가 있으면 componentDidMount 와 componentDidUpdate 둘다 수행 (업데이트아님)

 

useEffect(()=>{
	return()=>{ //unmount }
} , [ something ])

리턴부분이 componentwillUnmout

 

 

▶useEffect를 componentDidUpdate만 사용하고 싶은 경우 

    const flag =useRef( false );
    useEffect (()=>{
        if( !flag.current ){
            flag.current = true;
        }else{
            // update..event
        }
    },[ /*바뀌는값*/ ])

이런식으로 flag를 선언해서. (화면에보여줄게아니니 state가 아닌 useRef)

제일처음didMount때는 flag를 true 로만 바꾸고

update때만 실행되게 만들어서 사용할수 있다.

 

 

 

▶ex) 기존 클래스컴포넌트의 세가지 이벤트를 -> useEffect로 변경

    useEffect( ()=>{
        console.log(useEffect);

        //componentDidMount
        for( let i=0; i<pickNumbers.length-1; i++){ 
            timer.current[i] = setTimeout( ()=>{   
                setBalls( (prevBalls)=> [...prevBalls, pickNumbers[i]]   ) 
            } , (i+1)*100 ) ;
        }
        timer.current[6] = setTimeout(
            ()=>{
                setBonus(pickNumbers[6]);
                setRePick(true);
            } 
            ,700
        )

        return()=>{
            timer.current.forEach( (v)=>{ clearTimeout(v) }); //componentWillUnmount
        }
    },[balls.length === 0])  //componentDidUpdate조건

    // componentDidMount(){
    //     this.lottoStarting ();
    // }
    
    // componentWillUnmount(){
    //     this.timer.forEach( (v)=>{ clearTimeout(v) });
    // } 
    // componentDidUpdate( prevProps, prevState){
    //     if( this.state.balls.length === 0 ){
    //         this.lottoStarting ();
    //     }
    // }

이렇게 할경우.. balls의 초기값이 [] 여서, 

const [ballssetBalls] = useState([]);

버튼눌러서초기화시킬때 발생해야되는데 로딩될때 그냥 두번실행되는 문제발생.

 

 

조건을 바꾼다.

 

의존성배열에 timer.current를 넣어줌.

    useEffect( ()=>{ 
        //componentDidMount
        for( let i=0; i<pickNumbers.length-1; i++){ 
            timer.current[i] = setTimeout( ()=>{   
                setBalls( (prevBalls)=> [...prevBalls, pickNumbers[i]]   ) 
            } , (i+1)*100 ) ;
        }
        timer.current[6] = setTimeout(
            ()=>{
                setBonus(pickNumbers[6]);
                setRePick(true);
            } 
            ,700
        ) 
        return()=>{
            timer.current.forEach( (v)=>{ clearTimeout(v) }); //componentWillUnmount
        }
    },[timer.current])  //componentDidUpdate조건  ////////

초기값이

const timer = useRef([]);

이고

 

버튼클릭시

timer.current=[];

 인 타이머로 !

 

timer.current=[]; 이때 이전 current와 달라지는것.

timer.current[6] = someting;

※current배열에 요소로 넣어준 거라서 바뀌는게아님

 

그래서 바뀌는걸 =[] (빈배열대입시킬때) 감지함

 

 

 

 

 

 

 

성능최적화

훅스 자체 특성이 상태변할때 (훅스)함수 전체가 재실행 되서, 

이렇게 getPickNumbers 함수가 반복되서 호출되는 문제를 확인할 수 있는데,

 

이런 함수는 로또숫자들을 기억해두기위해서 useMemo를 사용한다.

 

 

 

 

함수실행한 결과값을 저장해 두려면

useMemo

 const lottoNumbers = useMemo( ()=>{   return //계속실행됬던문제의함수.   },[]);

이런식으로 만들어서 그 함수를 담아줌.

[] 의 인자가 바뀌지 않는한 useMemo에 담아둔 함수는 다시실행되지 않음

[] 의 인자가 바뀌면 다시실행됨.

 

************************************************

**그래서 만약 이렇게 다시버튼 눌렀을때

번호7개가져오는함수를 실행하지않고

useMemo에 저장된( 번호7개가져오는함수 결과값) 값을 붙여넣을경우

계속 같은 숫자가 리턴 됨 

************************************************

 

 

 

 

 

함수자체를 기억

useCallback

useCallback( ()=>{} , [] )

onClickRestart를useCallback으로 감싸면 onClickRestart이 함수자체를 기억

함수컴포넌트가 재실행 되도 그 함수를 재생성하지 않음.

 

여기도 마찬가지로 []의존성배열에 상태값바뀌면 다시발생시킬 상태값이름 넣어줘야

그 값이 업데이트 된다.

 const onClickRePick = useCallback( () =>{
 
        console.log( pickNumbers);

        setPickNumbers( getPickNumbers() );  //
}

위의 경우에서  getPickNumbers 로 계속해서 새로웃 숫자 7개를 받아와서

console.log 안에 pickNumbers 가 계속해서 받아온 새로운 숫자일 것으로 예상하지만,

(pickNumbers라는 애의 상태값을 의존성배열에 추가하지 않았으므로)

첫번째 배열을 기억해서 그것만 리턴한다

의존성배열에 추가하면 변화된 상태 반영 됨 ~~

 

 

 

 

useCallback을 필수로 적용해야될 경우

자식컴포넌트에 props로 함수를 넘길때는 useCallback을 써야,

자식컴포넌트에서 부모가 계속 함수 바꿀때마다, 매번 새로운props을 준다고

매번 리렌더링을 일으키지 않는다 

 

 

//Lotto.jsx
import React, { useState , useRef , useEffect, useMemo , useCallback} from 'react';
import Ball from './Ball';

function getPickNumbers(){
    console.log('pickNum START');
    const numArray = Array(45).fill().map( (v,i) => i + 1 );   
    const shuffle = [];
    while( numArray.length>0 ){    
        shuffle.push( numArray.splice(    Math.floor( Math.random() * numArray.length )   ,1)[0]  ); 
    }
    const bonusNumber = shuffle[shuffle.length -1]; 
    const rltNumbers = shuffle.slice(0,6).sort( (p,c)=>p-c );  
    return[...rltNumbers, bonusNumber];  
};

const Lotto = () =>{
    const lottoNumbers = useMemo( ()=>{  return getPickNumbers()   },[]);
    const [pickNumbers, setPickNumbers] = useState( lottoNumbers );
    const [balls, setBalls] = useState([]);
    const [bonus, setBonus] = useState(null);
    const [rePick, setRePick] = useState(false);
    const timer = useRef([]);
  
    const onClickRePick = useCallback( () =>{
        //초기화 
        console.log( pickNumbers);

        setPickNumbers( getPickNumbers() );  //
        setBalls([]);
        setBonus(null);
        setRePick(false);
        timer.current=[];
    } ,[pickNumbers])

    useEffect( ()=>{
        console.log("useEffect");
 
        for( let i=0; i<pickNumbers.length-1; i++){ 
            timer.current[i] = setTimeout( ()=>{   
                setBalls( (prevBalls)=> [...prevBalls, pickNumbers[i]]   ) 
            } , (i+1)*100 ) ;
        }
        timer.current[6] = setTimeout(
            ()=>{ setBonus(pickNumbers[6]);
                  setRePick(true); } ,700 )

        return()=>{
            timer.current.forEach( (v)=>{ clearTimeout(v) });  
        }
    },[timer.current])  
 
 
    return(
        <>
            <h1>당첨숫자</h1>
            <ul id="rlt_box">
                {  balls.map( ( v )=>( <Ball key={v} num={v} /> ) )} 
                    
                { bonus &&  <Ball num={bonus} />  }
            </ul>  
            { rePick && <button onClick={ rePick ? onClickRePick :()=>{} } >한번더</button> }
        </>
    ) 
}
export default Lotto;

 

 

 

 

기타

-Hooks는 순서가 중요하기때문에 조건문안에 넣지 않는다

-useEffect안에 useEffect넣으면 안됨.

 

 

 

 

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

13. (2) useReducer  (0) 2020.07.29
13. useReducer  (0) 2020.07.28
12. componentDidUpdate , setInterval  (0) 2020.07.27
11 (2)hooks / useEffect  (0) 2020.07.26
11. react life cycle  (0) 2020.07.26