익명의 개발노트

리액트 Hook 본문

프로그래밍/ReactJS

리액트 Hook

캡틴.JS 2019. 5. 8. 14:51
반응형

Hook 간다는 의미인가?

 

리액트 안에서 기본적으로 사용했던 클래스, state가 없을때 사용했던 함수형으로 했었는데.

 

리액트 훅은 함수형에 ref와 state를 사용할 수 있게 해준 거다.

 

실제로 써보면 클래스형 컴포넌트보다 훨씬 간결해짐을 볼 수 있다.

 

클래스형

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <script type="text/babel">
        class GuGudan extends React.Component {
            constructor(props){
                super(props);
                this.state={  //수동으로 바꿀값들만 여기다가 넣는다.
                    first: Math.ceil(Math.random() *9),
                    second: Math.ceil(Math.random() *9),
                    value : '',
                    result : '',
                };
            }   //end constructor 

            onSubmit =(e) => {
                e.preventDefault();
                if(parseInt(this.state.value) === this.state.first * this.state.second){
                    this.setState((prevState) => {
                        return {
                            result : '정답 : '+prevState.value, //이값은 과거 state값이고 나머지들은 현재값이라. 시점의 차이날 경우. setState() 안에 (prevState) => return {} 으로 값넣으면 좋다.
                            first: Math.ceil(Math.random() *9),
                            second: Math.ceil(Math.random() *9),
                            value : '',
                        }                      
                    });
                    this.input.focus();
                }else{
                    this.setState((prevState) => {
                        return {
                            result: prevState.value+'땡',
                            value : '',
                        }
                    });
                    this.input.focus();
                }
            };

            onChange = (e) =>{
                this.setState({value:e.target.value});
            };                
        

            render(){
               // console.log("랜더링"); setState하면 랜더링되기때문에 나중에 성능최적화해야함.
                return(
                    <React.Fragment>
                        <div>{this.state.first}곱하기{this.state.second}는?</div>
                        <form onSubmit={this.onSubmit}>
                            <input ref={(c) => {this.input = c;}} type="number" value={this.state.value} onChange={this.onChange}/>
                            <button>입력!</button>
                        </form>
                        <div>{this.state.result}</div>
                    </React.Fragment>
                )
            }
        }
    </script>
     <script type="text/babel">
        ReactDOM.render(<React.Fragment><GuGudan /></React.Fragment>, document.querySelector('#root'));
     </script>
</body>
</html>
함수형 훅 사용

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <script type="text/babel">

        //setState 사용하지 않는 것은 함수형. 훅스 : 함수형+ref, state 사용
        const GuGudan = ()=>{
            const [first, setFirst] = React.useState(Math.ceil(Math.random()*9));
            const [second, setSecond] = React.useState(Math.ceil(Math.random()*9));
            const [value, setValue] = React.useState('');
            const [result, setResult] = React.useState('');
            const inputRef = React.useRef(null);

            const onChangeInput = (e) =>{
                setValue(e.target.value);
            }

            const onSubmitForm = (e) =>{
                e.preventDefault();
                if(parseInt(value) === first * second){
                    setResult('정답 : '+value);
                    setFirst(Math.ceil(Math.random()*9));
                    setSecond(Math.ceil(Math.random()*9));
                    setValue('');
                    inputRef.current.focus();
                }else{
                    setResult('땡! 입력하신 결과는'+value);
                    setValue('');
                    inputRef.current.focus();
                }
            }

            return(
                <React.Fragment>
                    <div>{first}곱하기{second}는?</div>
                    <form onSubmit={onSubmitForm}>
                        <input ref={inputRef} type="number" value={value} onChange={onChangeInput}/>
                        <button>입력!</button>
                    </form>
                    <div>{result}</div>
                </React.Fragment>
            );            
        }


    </script>
     <script type="text/babel">
        ReactDOM.render(<GuGudan />, document.querySelector('#root'));
     </script>
</body>
</html>

왠만한건 변수로 선언해서 빼버림. 

 

편리해보인다.

 

참고자료 : 제로초님 유튜브 강좌(웹게임) https://www.youtube.com/watch?v=EUQnxfZgFJU&list=PLcqDmjxt30RtqbStQqk-eYMK8N-1SYIFn&index=11

 

 

1. useEffect : 라이프싸이클을 대신해준다.

2. useMemo : 메모이즈 기능으로 값을 저장해준다.

3. useCallback : 함수를 저장해준다.

클래스 사용

const React = require('react'); 
const { Component } =React;
const Ball = require('./Ball');

//숫자 미리뽑기
function getWinNumbers(){
    console.log('getWinNumbers');
    const candidate = Array(45).fill().map((v,i) => i+1);
    const shuffle =[];
    while(candidate.length>0){
        shuffle.push(candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]);        
    }
    const bonusNumber = shuffle[shuffle.length-1];
    const winNumbers = shuffle.slice(0,6).sort((p,c) => p-c);
    return [...winNumbers, bonusNumber];
}


class Lotto extends Component{
    state = {
        winNumbers : getWinNumbers(), //당첨숫자
        winBalls :[],
        bonus : null, //보너스공
        redo : false,
    }
    
    //비동기 관리할때 변수만들어서 담으면됨.
    timeouts = [];
    runTimeouts = () => {
        const {winNumbers} = this.state;
        for(let i =0; i<winNumbers.length -1; i++){
         this.timeouts[i] = setTimeout(()=>{
                this.setState((prevState)=>{
                    return {
                        winBalls:[...prevState.winBalls, winNumbers[i]]
                    };
                });
            }, (i+1) * 1000);
        }
        this.timeouts[6] = setTimeout(()=>{
            this.setState({
                bonus : winNumbers[6],
                redo : true,// 한번더 뽑을래?
            })
        }, 7000);
    }

    componentDidMount(){
       this.runTimeouts();
    }

    componentDidUpdate(prevProps, prevState){  
        console.log("리랜덩");     
       if(this.state.winBalls.length === 0){
           this.runTimeouts();
       }
      
    }

    componentWillUnmount(){
        this.timeouts.forEach((v)=>{
            clearTimeout(v);
        })
    }

    
    onClickRedo = () => {
        this.setState({
            winNumbers : getWinNumbers(), //당첨숫자
            winBalls :[],
            bonus : null, //보너스공
            redo : false,
        });
        this.timeouts=[];
        
    }


    render(){
        const {winBalls, bonus, redo} = this.state;
        return (
            <>
                <div>당첨숫자</div>
                <div id="result">
                    {winBalls.map((v)=> <Ball key={v} number={v}/>)}  
                </div>
                <div>보너스!</div>
                {bonus && <Ball number={bonus}/>}
                {redo &&  <button onClick={this.onClickRedo}>한번 더!</button>}
            </>
        )
    }
}


module.exports = Lotto;
Hooks 사용

const React = require('react');
const {useState, useRef, useEffect, useMemo, useCallback} = React;
const Ball = require('./Ball');

//훅 특성상 전체 다시 실행함. 
//getWinNumbers 의 로또부분 미리 저장해야함, memo
//useCallback 은 함수자체를 기억. memo는 값을 기억. 필수로 적용해야할 때, 자식컴포넌트에 함수넘길때는 반드시 해야함. 
//배열이 바뀌면 새로 다시 시작함.
//componentDidUpdate안에 if로 분기처리하면 useEffect도 분기처리된 수만큼 나눠서 처리해야함.

//숫자 미리뽑기
function getWinNumbers(){
    console.log('getWinNumbers');
    const candidate = Array(45).fill().map((v,i) => i+1);
    const shuffle =[];
    while(candidate.length>0){
        shuffle.push(candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]);        
    }
    const bonusNumber = shuffle[shuffle.length-1];
    const winNumbers = shuffle.slice(0,6).sort((p,c) => p-c);
    return [...winNumbers, bonusNumber];
}

const LottoHook =()=>{ //훅스는 선언하는 순서가 중요함. 바꾸면 안됨. 조건문안에는 절대로 넣으면 안됨. 훅스 안에 훅스 넣으면 안댐.
    const lottoNumbers = useMemo (()=> getWinNumbers(),[]);  //useMemo는 값을 기억한다 []이 바뀌기 전까지.
    const [winNumbers, setWinNumbers] = useState(lottoNumbers);
    const [winBalls, setWinBalls] = useState([]);
    const [bonus, setBonus] = useState(null);
    const [redo, setRedo] = useState(false);
    const timeouts = useRef([]);


    //ajax 같이 componentDidUpdate만 하고싶을때 쓰는 꼼수패턴
    const mounted = useRef(false);
    useEffect(()=> {
        if(!mounted.current){
            mounted.current = true;
        }else{
            //ajax
        }
    },[/*바뀌는값*/]); //이건 익혀두면 좋다.

    useEffect(()=>{
        runTimeouts();
        return  () => {
            timeouts.current.forEach((v)=>{
                clearTimeout(v);
            });            
        }
    },[timeouts.current]); //바뀌는 시점을 넣어주면됨.

    onClickRedo = useCallback(() => { //useCallback은 함수를 기억한다. []이 바뀌기 전까지. 
        console.log(winNumbers);
        setWinNumbers(getWinNumbers());
        setWinBalls([]);
        setBonus(null);
        setRedo(false);     
        timeouts.current=[];        
    },[winNumbers]); //바뀌어야하는걸 넣어줌. 안넣으면 winNumbers 처음값 그대로 가지고있음. 뭐 useCallback 안쓰면 상태는 초기화됨.

    runTimeouts = () => {                      
        for(let i =0; i<winNumbers.length-1; i++){
            timeouts.current[i] = setTimeout(()=>{
                  setWinBalls((prevBall) => [...prevBall, winNumbers[i]]);               
               }, (i+1) * 1000);
           }
           timeouts.current[6] = setTimeout(()=>{
               setBonus(winNumbers[6]);
               setRedo(true);            
           }, 7000);
    }

    return (
        <>
        <div>당첨숫자</div>
        <div id="result">
            {winBalls.map((v)=> <Ball key={v} number={v}/>)}  
        </div>
        <div>보너스!</div>
        {bonus && <Ball number={bonus}/>}
        {redo &&  <button onClick={onClickRedo}>한번 더!</button>}
    </>
    );
}

module.exports = LottoHook;

 

 

반응형

'프로그래밍 > ReactJS' 카테고리의 다른 글

웹팩 자동빌드하기  (0) 2019.05.09
웹팩(webPack) + 바벨 설정하기.  (2) 2019.05.08
Redux  (0) 2019.04.25
리액트 CSS관련 라이브러리  (0) 2019.04.24
리액트 라이프싸이클  (0) 2019.04.24
Comments