ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리액트 훅 useCallback와 useMemo
    React 2022. 8. 29. 23:16

    리액트에 작성하는 코드량이 늘어나게 되면서

    랜더링을 적절히 시켜주는 최적화에 대해서 생각하게 되었다.

     

    아직 제대로 적용까진 하지 못하지만

    조금 더 깔끔한 코드를 작성하기 위해서

    리액트 훅인 useCallbacckuseMemo에 대해

    알아보기로 했다.

     

    하지만 이 두개의 훅을 무조건 적으로 사용하는 것은

    오히려 렌더링 시간에 문제를 일으킬 수 있다고 한다.

    그 점은 알아두고 살펴보기로 하자.

     

    ※REACT의 리렌더링 조건

    1. 자신의 state가 변경될 때

    2. 부모 컴포넌트로부터 전달받은 props가 변경될 때

    3. 부모 컴포넌트가 리렌더링 될 때

     

    ex)

    APP.js
    
    function App() {
    	const [number, setNumber] = useState(0);
        return(
            <div>
                <div onClick={()=>{setNumber(number+1)}}>{number}</div>
                <Button/>
            </div>
    	);
    }
    
    export default App;

    이와 같이 숫자를 클릭하면 숫자가 state를 통해 숫자가 1씩 증가하는 앱이다.

    여기서 버튼 컴포넌트는 아무 변화없는 App 컴포넌트의 자식 컴포넌트일 뿐이라고 할때

    부모 컴포넌트가 리렌더링 될 때 항상 리렌더링된다.

     

    현재는 성능에 지장이 없지만 복잡하고 값비싼 코드를 포함하는

    컴포넌트라면 리렌더링을 최대한 줄여야한다.

    이러한 최적화를 위해 React.memo를 사용한다.

     

    React.memo란..?

    말 그래도 컴포넌트를 메모이제이션해준다.


    ※메모이제이션(memoization)은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 동적 계획법의 핵심이 되는 기술이다. 메모이제이션이라고도 한다.

     

    부모 컴포넌트로 넘겨받는 props가 같다면 메모이제이션 해둔 렌더링 결과를 가져온다. 메모이제이션한 내용을 재사용하여 렌더링시 가상 DOM에서 달라진 부분을 확인하지 않아 성능상의 이점이 생기게 된다.

     

    사용법

    컴포넌트를 React.memo로 감싸면 된다

    // Button.js
    
    function Button() {
    	return (
        <button className="button">RESET</button>
        )
    }
    
    export default React.memo(Button);

     

    하지만 메모제이션을 해주어도 리렌더링이 발생하는 경우가 있다.

    부모 컴포넌트에서 props로 객체를 넘겨받은 경우

     

    부모 컴포넌트가 리렌더링 될 때 객체가 값이 같더라도 주소값이 다른 새로운 객체를 계속 생성하며, React.memo는

    부모 컴포넌트로부터 넘겨받은 props가 변경되었다고 판단하여 계속 리렌더링 하게 된다.

     

    이러한 불필요한 렌더링과 계산을 방지하기 위해

    useCallbackuseMemo가 설계되었다.

     

    useCallback과 useMemo의 차이

    useCallback과 useMemo는 비슷한 Hook이라고 볼 수 있다. 

    useMemo는 특정 결괏값을 재사용할 때 사용하며

    useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용하는 함수이다.

     

    useCallback

    언제나 동일한  함수를 return해준다.

    const Callback = useCallback(()=>{
    	doSomething(a,b);
        },
        [a,b],//deps
    );

     

    제공된 deps를 기준으로 반환된 함수 객체를 메모이제이션 하는 것 뿐이다.

    즉, 동일한 deps가 제공되면 (참조가 비교) 동일한 함수 객체를 반환한다.

     

    따라서 React.mome로 감싸준 자식 컴포넌트에게 함수를 props로 넘겨줄 경우,

    넘겨받는 함수를 useCallback으로 감싸주면 deps 가 바뀌지 않는 한

    항상 동일한 객체를 넘겨줌으로 불필요한 리렌더링을 방지한다.

     

    만약 그냥 새로운 함수를 매번 만드는 대신 'useCallback'으로 선언된 함수를 컴포넌트나 훅으로 넘

     

    useMemo

    useMemo 또한 useCallback과 동일한 방식으로 사용하면 된다.

     

    const memoizedValue = useMemo(() => computeEx[emsoveValue(a,b),[a,b]);

     

    상황을 예로 들어

    //App.js
    
    function App () {
    	const[number, setNumber] = useState(0);
    	return(
        <div className="App">
        	<div className="num" onClick={()=>{setNumber(number+1)}}>{number}</div>
            <Button onClick={onClick} style={{backgroundColor: 'darkseagreen'}}/>
        </div>
        );
    }
    
    export default App;
    
    //버튼 컴포넌트가 있고 onClick과 style={{backgroundColor:'darkseagreen'}}
    을 props로 내려준 상황

    이 또한 div style 자체는 바뀌지 않는데

    className=num 이라는 div를 클릭하면

    숫자가 증가하면서 리렌더링되고

    그에 따라 자식컴포넌트인 버튼에 인라인으로 들어간

    객체가 새로운 객체가 생성되면서

    버튼또한 리렌더링 될 것이다.

     

    이때 사용하는 것이 useMemo이다

    //App.js
    
    function App () {
    
    	const buttonStyle = useMemo(() => ({backgroundColor: 'darkseagreen'}),[]);
    	const[number, setNumber] = useState(0);
    	return(
        <div className="App">
        	<div className="num" onClick={()=>{setNumber(number+1)}}>{number}</div>
            <Button onClick={onClick} style={buttonStyle}/>
        </div>
        );
    }
    
    export default App;
    
    //버튼 컴포넌트가 있고 onClick과 style={{backgroundColor:'darkseagreen'}}
    을 props로 내려준 상황

    이렇게 해주면 props로 렌더링 전 값과 주소까지 일치한 값이 되기 때문에

    리렌더링을 방지할 수 있다.

     

    그리고 이때

    deps가 빈 배열인 이유는 위의 예시에서는 해당 함수나 값이 의존하는 변수가 없기 때문이다. 의존하는 값이 존재하는 경우에는 반드시 deps 배열안에 명시해줘야 한다.

Designed by Tistory.