본문 바로가기

블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기

[85일차 복습] React Hooks useMemo useCallback

반응형

 

1. React Class 형으로 버튼을 눌러서 up down 되도록 만들기 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>REACT TIME</title>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

</head>

<body>
    <h2>up and down btn</h2>
    <div id="root"></div>
    <script type="text/babel">



        class CounterClass extends React.Component {
            state = {
                number : 0 
            }

            up =()=>{
                this.setState({
                    number: this.state.number +1
                })
            }

            down =()=>{
                this.setState({
                    number : this.state.number -1 
                })
            }

            render() {
                return (
                    <>
                        <h3>{this.state.number}</h3>
                        <button onClick={this.up}>-1</button>
                        <button onClick={this.down}>+1</button>
                    </>
                )
            }
        }


        ReactDOM.render(
            <CounterClass />,
            document.querySelector('#root')
        )

    </script>
</body>

</html>

클래스형은 바뀐부분이 생기면 render() 부분만 재 랜더가 된다. 

 

2. 위의 내용을 함수형 Function으로 다시 만들어 보기 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>REACT TIME</title>
    <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

</head>

<body>
    <h2>up and down btn</h2>
    <div id="root"></div>
    <script type="text/babel">

        const CounterFn = ()=>{
            const [number, setNumber] = React.useState(0)

            const down = () => {
                setNumber(number-1)
            }

            const up = () => {
                setNumber(number+1)
            }

            return(
                <>
                    <h3>{number}</h3>
                    <button onClick={down}>-1</button>
                    <button onClick={up}>+1</button>
                </>
            )
        }

            ReactDOM.render(
            <CounterFn />,
            document.querySelector('#root')
        )

    </script>
</body>

</html>

함수형의 경우 setState의 일부분이 바뀌면 전체가 모두 재랜더링이 된다. (연관없는 함수들, 코드까지) -> 함수형으로 많은 연산을 포함하게되면 안좋음 -> 이제 요거를 커버할 수 있는 Hooks 를 배울 것 ! 

 

 

 

 


 

 

Memoization

메모이제이션이란 ? 컴퓨터가 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산을 할 필요가 없어져 실행 속도가 빨라지는 기술 -> 동적 계획법(DP)의 핵심 기술

하지만 메모리를 사용하기 때문에 속도를 받고 '성능'을 준 셈 (자원의 교환) -> 위의 기술을 너무 남발하게 되면 성능이 저하된다. 꼭 필요한 부분에 사용하기 ! 

 

 

메모이제이션을 실행해보기 좋은 예, 피보나치 수열이 있다. 

 

피보나치 수열이란 ? 

피보나치 순열 

첫 째항, 둘 째항이 무조건 1 이며 그 뒤로 이전의 n-1, n-2 의 위치의 값을 더해서 n 값을 만드는 수열 

 

피보나치 수열을 JS 알고리즘으로 표현하면

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script type="text/javaScript">
        function fibo(n){
            if(n==1) return 1;
            if(n==2) return 1;

            return fibo(n-2) + fibo(n-1);
        }
    </script>
    
</body>
</html>

그런데 숫자가 n = 50만 넣어도 작동이 아~주 오래 걸린다. 재귀함수로 계속 돌고 있기 때문에 효율이 안좋다. 

예를 들어, 

 

n = 3일 때 fibo() 함수는 2번이 실행되고 n = 4일 때는 fibo() 함수가 4번이 실행된다. 

n = 5일 때는 fibo() 함수가 8번, n = 6일 떄는 14번이나 재귀함수가 실행된다.

 

만약 이전에 계산한 것을 메모리에 저장하고 필요할 때 메모리에 저장된 값을 꺼내서 사용하면 ? -> 메모이제이션 ! 

위의 비효율성을 줄일 수 있다. 즉, 한 번 연산했던 이전의 값을 저장해두었다가 재사용하는 기술 

 

memo 라는 곳에 연산이된 값을 넣고 n 연산을 할 때 해당 n이 memo 안에 있다면 연산하지않고 해당 값을 꺼내오기만 하면 된다. 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script type="text/javaScript">
        let memo = {}

        function fibo(n){
            let result;

            if(n in memo){
                result = memo[n]
            }else{
                if(n==1 ||n==2){
                    result = 1;
                }else{
                    result = fibo(n-2)+fibo(n-1)
                }
                memo[n]= result;
            }
            return result;
        }
    </script>
    
</body>
</html>

 

 

Memoization 만들어보기 

 

1. 웹팩 수동 설치된 기본 코드 준비 (index.jsx, App.jsx, webpack.config.js, package.json)

App Component는 함수형으로 ! 

https://blckchainetc.tistory.com/255

 

[79일차 복습] 웹팩 CRA 없이 React 개발 환경 구축 및 핫리로드 & CRA 사용버전

Webpack, 웹팩이란 ? -> https://blckchainetc.tistory.com/253 웹 애플리케이션의 빠른 로딩을 " data-og-host="blckchainetc.tistory.com" data-og-source-url="https://blckchainetc.tistory.com/253" data-og..

blckchainetc.tistory.com

 

 

 

2. memo 폴더, memo.jsx 파일 생성, 코드작성, App과 연결 

 

이제 

<- 요 기본틀을 자동으로 구현되게 만들기 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3. 내용쓰고 추가 누르면 리스트 추가되도록 만들기 

1) input box onChange, value 변동시키기

2) submit 제출하면 input box reset 

3) <li>로 배열로 담으면 jsx가 알아서 리스트로 뿌려줌 -> 배열에 담아 map으로 뿌리기

 

전체 코드

import React, {useState} from 'react';

const Memo =() =>{
    const [username, setUsername] = useState('');
    const [list,setList] = useState([]);


    const changeValue = (e) =>{
        setUsername(e.target.value)
    }

    const submit =(e)=>{
        e.preventDefault()
        let new_list = [...list]
        console.log(new_list)
        new_list.push(username)
        setList(new_list)
        setUsername('')
    }

    const renderList =()=>{
        return(
            list.map((v,i)=>{
                return <li key={i}>{v}</li>
            })
        )
    }


    return(
        <>
            <h2>회원리스트({list.length})</h2>
            <form onSubmit={submit}>
                <input type="text" name="username" value={username} onChange={changeValue}/>
                <button type="submit">
                    추가 
                </button>
            </form>
            <ol>
                {renderList()}
            </ol>
        </>
    )
}

export default Memo

 

 

list의 요소를 하나하나 <li>에 담아 배열로 만들어 return 하는 3가지 방법 

1)

         let V = list.map((v,i)=>{
             return <li key={i}>{v}</li>
         })
         return V

2) 

        return(
            list.map((v,i)=>{
                return <li key={i}>{v}</li>
            })
        )

3)

        let newArr = [];
        for (let i=0; i<list.length; i++){
            newArr.push(<li key={i}>{list[i]}</li>)
        }
        return newArr

 

 

 

 

 

 

useMemo

 

 

위에서 함수형, 클래스형 차이를 설명한 것처럼 함수형의 경우 한 부분인 setState가 되면 모든 전체 코드가 rerender 되어 비효율적이다. useMemo 를 사용해서 메모리에 메모하듯이 값을 저장해놓고 값의 변경이 일어날 경우만 render되도록 만드는 것이 효율적 !  위의 코드에서 onChange 함수의 경우, inputbox에 글자를 쓸 때마다 rerendering 되고 있어서 이 부분을 useMemo로 수정해보기 

 

 

회원리스트 ({list.length})부분 useMemo로 수정하기

 

1. useMemo 'react' 로부터 가져오기 (react.useMemo() 처럼 매서드가 react안에 존재 -useState처럼)

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

2.  useMemo 사용하기 

    const userCount = useMemo(()=>{
        return list.length
    }, [list.length])

    return(
        <>
            <h2>회원리스트({userCount})</h2>
            <form onSubmit={submit}>

useMemo의 첫 번째 인자 : 함수

                 두 번째 인자 : 변경 감지할 값 

두 번째 인자에 넣은 list.length 값이 변경이 되면 해당 값을 메모리에 저장하고 ---> render

 

사실 list의 length값 변경은 '추가' 버튼을 눌러야 변경(render)되는 것 같지만 input change 할 때마다 모든 함수들이 재선언되고 있다. 

 

함수에서 useMemo, 클래스의 경우 비슷한 기능으로 pure Component가 있다고 한다.

 

 

 

 

 


 

 

 

useCallback

useCallback과 useMemo의 차이 :

useCallback은 함수, Function을 memoization, 재사용할 때 사용 

useMemo는 변수의 값 (특정 결과값)을 memoizationg, 재사용할 때 사용 

 

 

 

 

1) useCallback 가져오기 

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

2) useCallback 을 onChange 함수에 사용해보기 

    const input_userName=useCallback((e)=>{
        setUserName(e.target.value)
    },[])
    
    .
    .
    .
    
    
                <h2>회원리스트({userCount})</h2>
            <form onSubmit={submit}>
                <input type="text" name="userName" value={userName} onChange={input_userName}/>

useCallback도 첫 번째 인자값: 함수 , 두 번째 인자값  : 변화 감지 기본값

[] 빈 배열은 가장 처음 Component를 실행하고  render될 때 input_userName 함수를 생성하겠다. 라는 의미라고 함

근데 저 빈 배열이 없어도 잘 되어서 아직 정확히 잘 모르겠다. username의 값은 string인데... 

 

* google에서 찾아본 useCallback

useCallback을 사용하지않은 함수 컴포넌트들은 rerendering 될 때마다 새로 만들어진다. 함수를 선언하는 자체는 메모리, CPU 리소스를 많이 차지하는 작업은 아니어서 함수를 매번 새로 선언한다고 그 자체만으로 큰 부하가 생기지는 않지만 useCallback을 사용해서 저장해두고 필요할 때 재사용하는 것은 필요하다. 

이유 : 최적화 

 

 

3) submit 함수를 useCallback 해보기

    // const submit = (e)=>{
        // e.preventDefault()
        // let new_list = [...list]
        // new_list.push(userName)
        // setList(new_list)
        // setUserName('')
    // }

    const submit = useCallback((e)=>{
        e.preventDefault()
        let new_list = [...list]
        new_list.push(userName)
        setList(new_list)
        setUserName('')
    },[list, userName])



    return(
        <>
            <h2>회원리스트({userCount})</h2>
            <form onSubmit={submit}>

안의 내용은 똑같고 초기 값(두 번째 인자값) 에 [list, userName]을 넣어준다. submit 함수의 내용을 보면 setList, setUserName으로 해당 값들을 변경하고 있어서 두 개 모두 넣기 ! 

 

 

4) listRender함수도 변경해보기

    // const listRender=()=>{
    //     return(
    //         list.map((v,i)=>{
    //             return <li key={i}>{v}</li>
    //         })
    //     )
    // }

    const listRender = useCallback(()=>{
        return(
            list.map((v,i)=>{
                return <li key={i}>{v}</li>
            })
        )
    },[list])

근데 두 번째 인자값 [list]를 빼도 실행은 되는데 이 점이 궁금하다. 

 

 

 

 


 

 

 

* useCallback의 두 번째 인자 



두번째 인자의 배열** 은 의존성을 의미 - useCallback 함수 내에서 의존하는 상태값이 있다면 반드시 두 번째 인자 배열에 명시해야 한다. 

 

요 부분 아래를 보면 두 번째 인자값 이해가 잘 간다. 위의 전체 코드에 버튼들과 count 변수 추가해서 실험해보기 

 

useCallback 의 두 번째인자값을 안넣었을 때 

   const Memo = () =>{
    const [userName, setUserName] = useState('')
    const [list, setList] = useState([])
    const [count, setCount] = useState(0)
    .
    .
    .
    
   
   const up = useCallback(()=>{
        setCount(count+1)
    },[count])

    const consoleLog = useCallback(()=>{
        console.log(count)
    },[])
    

    return(
        <>  
            <p>{count}</p>
            <button onClick = {up}>+1</button>
            <button onClick= {consoleLog}>console.log찍기</button>

실행 순서 

1) console.log찍기 버튼 클릭 

2) +1 버튼 클릭

3) 다시 console.log찍기 버튼 클릭 

 

 

1) 번 0 출력 

2) 번 클릭 -> count +1 = 1

3) 번 클릭 -> 0 출력 

 

두 번째 인자값이 없어서 참조할 곳이 없다. 함수 안의 {count}는 최초 설정(첫 render)했을 떄 기본값이었던 0 을 계속 return 한다. 

 

 

count의 default값을 10으로 한 경우도 +1을 여러번 해도 계속 default 값 출력 

 

 

 

두 번째 인자값을 넣었을 때 

    const [count, setCount] = useState(0)


const consoleLog = useCallback(()=>{
        console.log(count)
    },[count])
    
    
                <p>{count}</p>
            <button onClick = {up}>+1</button>
            <button onClick= {consoleLog}>console.log찍기</button>

 

함수 실행할 때 참조할 count 값이 배정되어 있어서 해당 component 안의 count를 가져와 console.log를 찍을 수 있다. 

 

 

끄읏 

반응형