Memoization : useCallback, useMemo, memo

wonkeunC·2021년 11월 2일
0

React

목록 보기
4/4
post-thumbnail

최적화에 대해 얘기해 보자

최적화는 웹 또는 앱이 느려졌을 경우에만 시행한다. 보통 무거운 컴포넌트를 다룰 때 시행하는데 무거운 컴포넌트라 함은
1. 계산을 수행하는 것
2. API에 요청을 보내는 것
3. 브라우저의 이벤트를 듣는 것

이런 것들을 앱 속도를 느리게 하고 최적화할 필요성이 있는 것이다.
주의! 최적화가 필요 없는 상황인데 할 경우에 시간 낭비나 다름없다! 필요할 때만 최적화에 시간을 들이길!

Memoization :
이전 값을 메모리에 저장해 동일한 계산의 반복을 제거해 빠른 처리를 가능하게 하는 기술.
useMemo, useCallback, React.memoMemoization을 기반으로 작동한다.


컴포넌트는 언제 렌더링 될까?

  1. 컴포넌트가 렌더링 된다는 것은 그 함수(컴포넌트)를 호출하여서 실행되는 것을 의미한다.
    함수 즉 컴포넌트가 실행될 때마다 내부에 선언되어 있던 표현식(변수, 또다른 함수 등)도 매번 다시 선언되어 사용된다.

  2. 컴포넌트는 자신의 state가 변경되거나, 부모에게서 받은 props가 변경되었을 때마다 re-rendering 된다. (심지어 하위 컴포넌트에 최적화 설정을 해주지 않으면 부모에게서 받은 props가 변경되지 않았더라도 re-rendering 되는게 기본이다.)



React.memo

React.memo는 고차 컴포넌트(Higher Order Component) 이다.

고차 컴포넌트 란?
컴포넌트 로직을 재사용하기 위한 React의 고급 기술이다. 고차 컴포넌트(HOC)는 React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴입니다.
쉽게 말해서, 고차 컴포넌트는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수이다.

React는 먼저 컴포넌트를 렌더링(rendering) 한 뒤, 이전 렌더된 결과와 비교하여 DOM 업데이트를 결정한다. 만약 렌더 결과가 이전과 다르다면, React는 DOM을 업데이트한다.

다음 렌더링 결과와 이전 결과의 비교는 빠르다. 하지만 어떤 상황에서는 이 과정의 속도를 좀 더 높일 수 있다.

쉽게 말해서, React.memo는 memo를 적용한 컴포넌트의 props의 값이 변화가 없다면 해당 컴포넌트는 rendering을 안하게 끔 해주는 것이다.

  • 현재 상황 : 일반적으로 부모 컴퍼넌트에 의해 하위 컴퍼넌트가 같은 props로 리렌더링 될 때가 있다. (React.memo 사용! )
    • 부모 컴포넌트 -> 별점 순, 리뷰 많은 순 클릭 시 하위 컴포넌트 -> 슬라이더 버튼 함수 컴포넌트도 렌더링 발생.

여기서는 사용되지 않는 불필요한 SliderBtn 컴포넌트가 별점 순, 리뷰 많은 순 버튼을 누를때마다 계속해서 같이 rendering 하는 것을 볼 수 있다. ( SliderBtn은 불필요한 rendering )
그렇기 때문에 SliderBtn에 memo를 적용해줌으로써 SliderBtn 컴포넌트가 렌더 되는 상황을 막을 수 있다.

export default memo(SliderBtn);

🔎 memo 적용 후


적용 원리 !

// SliderBtn은 자식 컴포넌트이다. 부모 컴포넌트에서 전달 받은 props는 
// stationToTalNum, driverTotalNum, optionId, slideRef 4가지가 있다.
const SliderBtn = ({ stationToTalNum, driverTotalNum, optionId, slideRef }) => {...}

memo를 사용하게 되면 부모 컴포넌트로 부터 전달 받은 4가지 props중 1가지라도 값이 변경되면 자식 컴포넌트인
const SliderBtn = ({...}) 는 re-rendering 하게 된다.

하지만 props의 값이 변경되지 않고 이전이랑 동일 한 경우에는 위 사진처럼 SliderBtn 컴포넌트 전체의 내용을 흝지 않고(rendering) 메모이징(Memoizing)된 내용을 재사용한다.


useCallback

컴포넌트는 렌더링 될 때 마다 함수를 새로 생성한다는 단점이 있다. 부모 컴포넌트가 렌더링되거나, 상태(state)가 변경되는 경우, React 컴포넌트는 렌더링을 유발한다.

컴포넌트가 재실행 되어도 그 안에 있는 함수들 중 useCallback()을 사용한 함수는 재실행 되지 않는다!
컴포넌트 내부에 복잡한 계산을 하는 함수가 있거나 return 값을 내는데 시간이 걸리는 함수가 있을 경우에 useCallback를 이용한다.
컴포넌트는 render 될 때마다 컴포넌트 안에 있는 모든 내용물도 새로 만들어지는데 이는 복잡한 함수가 있을 경우 화면에 나타낼 때까지 시간이 소모된다. 이를 막기 위해 복잡한 계산식 함수에 useCallback()을 적용하고 처음 render 될 때만 함수를 생성하고 컴포넌트가 re-rendering 될 때 컴포넌트는 useCallback()을 적용한 함수는 새로 만들지 않고 그 함수를 기억하고 re-rendering 될 때 함수를 재사용한다.

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

const [surName, setSurName] = useState('');
const [firstName, setFirstName] = useState('');

const onChangeHandler = useCallback((e) => {
 if (e.target.id === "surName"){
   setSurName(e.targer.value);
 }
 else{
   setFirstName(e.target.value);
 }
},[])

useCallback을 import 하고 onChangeHandler 함수를 작성해 보았다.
첫 마운트 될 때만 메모리에 할당되었는지 아닌지 확인하기는 어렵겠지만 위와 같이 useCallback이 사용된다. 예시와 같은 이벤트 핸들러 함수나 api를 요청하는 함수를 주로 useCallback을 사용한다.

하지만, 복잡한 계산이 아니면 useCallback, useMemo 사용을 권장하지 않는다고 React 가이드북에 나와있는 것처럼 이 정도 수준의 함수 재선언을 막기 위해 useCallback을 사용하는 것도 크게 의미 있지 않는다.

어쩔 때 사용하면 좋을까?

  • 앞서 말한 것 처럼 복잡한 계산을 하는 함수에 적용하고 또는 하위 컴포넌트가 React.memo() 같은 것으로 최적화되어 있고 그 하위 컴포넌트에게 callback 함수를 props로 넘길 때, 상위 컴포넌트에서 useCallback으로 함수를 선언하는 것이 유용하다. 함수가 매번 재선언 되면 하위 컴포넌트는 넘겨받은 함수가 달라졌다고 인식하기 때문이다.

profile
개발자로 일어서는 일기

0개의 댓글