React | useMemo(), useCallback(), + React.memo()

앙두·2023년 2월 15일
0

React

목록 보기
15/20

2023 / 07 / 18
➕ 되게 심플하고 정확하게 React.memo()와 useMemo()를 정리한 블로그 추가 ( 헷갈릴때마다 보기 )
https://sustainable-dev.tistory.com/137


📋 useMemo()

useMemo는 컴포넌트의 성능을 최적화시킬 수 있는 대표적인 react hooks 중 하나!
useMemo에서 Memo는 Memoization을 뜻한다.

* memoization ? 메모이제이션 ?
: 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술!
: 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법!

useMemo의 동작 순서

렌더링 ➡️ Component 함수 호출 ➡️ Memoize된 값을 재사용

useMemo는 첫번째 인자로 콜백함수를, 두번째 인자로 의존성 배열을 받는다.

const memo = useMemo(()=>{}, [])

의존성 배열요소 값이 업데이트 될 때만,
콜백함수를 호출 ➡️ memoization 된 값을 업데이트 ➡️ 그 값을 다시 re-memoization ➡️ 재활용

(만약 빈 배열을 두번째 인자로 넘겨주면,
맨 처음 컴포넌트가 마운트 됐을 때만 값을 계산하고, 이후에는 memoization된 계산된 값을 꺼내와서 사용함)

const value = useMemo(()=>{
		return calculate();
	}, [item])

👧🏻☝🏻 useMemo(), 꼭 필요할 때만 사용하세요!
: 값을 재활용하기 위해 따로 메모리를 소비해서 저장을 해놓는 것! 그렇기 때문에 불필요한 값을 모두 Memoization을 해버리면 성능이 안 좋아질 수 있기 때문에 필요할 때만 사용해야 한다!


🤚🏻 useCallback()

useCallback()도 함수를 메모이제이션(memoization)하기 위해서 사용되는 함수.
첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 기억/저장해놓고 재사용할 수 있게 해줌.
( + useMemo는 값을 재활용한다면, useCallback은 함수를 재활용한다 )

const memorizationCallBakc = useCallback(함수, 의존성배열)

예를 들어,
어떤 컴포넌트 안에 함수가 선언이 돼있다면, 그 함수는 해당 컴포넌트가 렌더링될 때마다 새로운 함수로 생성되며 실행된다.

const a = () => x + y;

-> 렌더링 될 때마다 새로운 함수로 생성 및 실행

하지만,
useCallback()을 사용하면, 해당 컴포넌트가 렌더링되더라도 그 함수가 의존하는 값들이 바뀌지 않는 한 기억/저장해놓은 함수를 기존으로 계속해서 반환한다.
의존값들이 바뀌면 새로운 함수가 생성돼 변수에 할당되고, 의존값들이 동일하면 다음 렌더링에도 기억/저장해놓은 함수를 재사용한다.

const a = useCallback(() => x + y, [x, y])

-> x, y 값이 바뀌면 새로 생성되어 a 에 할당 / x, y 값이 바뀌지 않으면 함수 재활용

➕ React.memo()와 useCallback()

useCallbakc() hook함수는 자식 컴포넌트의 불필요한 렌더링을 줄이기 위해 React.memo() 함수와도 함께 사용할 수 있다.

Child = React.memo(Child)

예를 들어, props를 받은 자식컴포넌트(Child)가 있다고 가정해보자.
React.memo() 함수로 해당 컴포넌트를 감싸준다.
이렇게 감싸주면 해당 컴포넌트는 props 값이 변경되지 않는 한 다시 호출되지 않는다.

export default function ChildCpnt() {
	...
	return (
    	<>
    		<Child toggle={toggleSwitch1} />
			<Child toggle={toggleSwitch2} />
			<Child toggle={toggleSwitch3} />
    	</>
   );
}

그러나 이처럼 Child 컴포넌트가 여러개가 있을 경우에는,
한개의 Child 컴포넌트만 실행시켰더라도 toggle에 할당된 함수의 참조값이 해당 컴포넌트가 렌더링될 때마다 바뀌기 때문에, 3개의 모든 Child가 렌더링이 돼버린다.

이 문제를 해결하려면 모든 토클 함수를 useCallback() hook 함수로 감싸고, 각 함수가 의존하고 있는 상태값을 배열에 담아주어야 한다. 👇🏻

const [switch1, setSwitch1] = useState(false);
const [switch2, setSwitch2] = useState(false);
const [switch3, setSwitch3] = useState(false);

const toggleSwitch1 = useCallback(()=> setSwitch1(!switch1) ,[switch1])
const toggleSwitch2 = useCallback(()=> setSwitch2(!switch2) ,[switch2])
const toggleSwitch3 = useCallback(()=> setSwitch3(!switch3) ,[switch3])

useCallback() 함수가 할당받은 함수를 기억/저장해놓기 때문에 렌더링이 되어도 함수의 참조값이 바뀌지 않는다!

React.memo() 함수로, props가 바뀔 때만 해당 컴포넌트가 렌더링 되게 하고
useCallback() 함수로, 해당 컴포넌트 내 함수들이 의존값 변경에 따라서만 렌더링 될 수 있게끔 불필요한 렌더링을 줄여 성능을 최적화 시켜주는 것이다.

그러나 성능 최적화에는 그에 상응하는 대가가 따른다고 한다 🥲
최적화된 코드를 만들어본 적이 없어서 ^^ 아직 경험해보지는 않았찌만,
코드가 복잡해진다던지 유지보수가 어려워질 수 있다.

따라서 useCallback() 함수나, React.memo() 함수를 사용하기 전에
실질적으로 얻을 수 있는 성능 이점이 어느 정도인지 반드시 직접 측정을 해보고 사용하는 것이 가장 베스트라고 한다!

각 함수의 장단점도 명확히 미리 알고 판단하는 것이 좋은 것 같다 🤔

profile
쓸모있는 기술자

0개의 댓글