리액트 최적화(useMemo,React.memo,useCallback)
최적화란 웹 서비스의 성능을 개선하는 것을 말한다.
웹 서비스의 성능을 개선하기 위해서는 코드, 폰트, 이미지 등의 사이즈를 줄이거나, 페이지 로딩속도를 빠르게 하거나, 불필요한 연산을 다시 수행하지 않게 하는 등등의 행동을 할 수 있다.
불필요한 연산을 다시 수행하지 않게 해서 페이지 렌더링 속도를 빠르게 만들어주는 다양한 리액트 내부 최적화 기법을 살펴본다.
재연산을 방지하는 useMemo
useMemo는 특정 조건을 만족하지 않으면 컴포넌트가 리렌더링 되더라도, 어떤 연산들을 다시 실행하지 않게 도와준다
useMemo()
useMemo의 첫 번째 인수로는 콜백함수를 넣어주고, 콜백함수 안에는 특정 조건에서만 다시 실행시키고 싶은 연산을 넣어준다. 두 번째 인수로는 의존성 배열(Deps)을 전달하면 된다.
이렇게 하면 의존성 배열로 전달된 todos의 값이 변경되었을 때만 첫 번째 인수로 전달된 콜백함수를 다시 실행시킨다.
useEffect와 비숫하지만 차이점은 useMemo는 첫 번째 인수로 전달한 콜백함수가 반환하는 값들을 반환한다.
컴포넌트의 리렌더를 방지하는 React.memo
(컴포넌트를 props를 기준으로 최적화하는 방법)
컴포넌트가 불필요하게 리렌더 되고 있는 상황이라면 React.memo를 통해 방지할 수 있다.
React의 memo라는 메서드는 인수로 컴포넌트를 전달받고, 최적화시킨 새로운 컴포넌트를 반환하는 메서드이다. 위처럼 사용하면 OptimizedHeaderComponent라는 변수에는 최적화된 Header 컴포넌트가 저장된다.
이때 최적화의 원리는 부모 컴포넌트로부터 제공받는 props가 변경되지 않으면, 부모 컴포넌트가 리렌더 되더라도, 다시 리렌더 되지 않도록 하는 것이다.
즉 기존에는 부모컴포넌트가 리렌더 되면 자식컴포넌트도 그냥 따라서 리렌더 되었지만, React.memo를 사용하면 이를 방지할 수 있는 것이다.
Header 컴포넌트는 어떠한 props도 가지고 있지 않기에, props값이 변경될 일이 없으며, 이에 따라 부모 요소가 리렌더링 되어도 Header 컴포넌트는 리렌더링 될 일이 없을 것이다.
위의 코드를 아래처럼 줄여서 써도 상관없다.
자바스크립트에서는 자료형, 값의 타입을 한 번에 하나의 값만 표시하는 원시 자료형(숫자, 문자열, 불리언, Null, Undefiend)과 한 번에 여러 개의 값을 저장 또는 표현할 수 있는 참조 자료형(객체, 배열, 함수)으로 분류한다.
원시 자료형은 변수에 값 자체를 저장하지만, 참조 자료형은 변수에 참조값(주소값)을 저장한다.
그렇기에 위처럼 a와 b를 비교하면 false가 나온다.
만약 React.memo를 통해 최적화를 시킨 컴포넌트가 있다고 하자. 이때 props 값의 변경이 없음에도 불구하고 계속해서 리렌더링이 된다면 props로 참조 자료형이 전달되고 있지는 않은지 확인해 보면 된다.
부모 컴포넌트 A와 자식컴포넌트 B가 있다고 하자. A에서 변수에 함수를 저장하고, 그 변수를 B에게 props를 통해 전달한다. 이때 B는 React.memo를 통해 최적화를 시킨 상태이다.
만약 A가 리렌더링 되면 B에 전달하는 props 값은 변화가 없을 것이기에, 최적화된 B는 당연히 리렌더링이 안될 것이라고 예상할 수 있지만 B 또한 마찬가지로 리렌더링이 된다.
이유는 다음과 같다. 변수에 저장된 함수는 참조 자료형이므로 변수에 직접적인 값이 저장되는 것이 아니라, 참조값(주소값)이 저장된다. 즉 A가 리렌더링 될 때마다 함수도 다시 호출되면서 그때마다 참조값이 계속해서 변경되고, 결론적으로 B에 전달되는 참조 값도 변하는 것이다.
그렇기에 B의 React.memo는 A로부터 받는 props가 변경되었다고 판단해서 리렌더링을 막지 않는 것이다.
만약 B의 리렌더링을 막고 싶다면 A가 리렌더링 될 때 해당 함수가 다시 호출되지 않게 막으면 된다. 이를 위해서 useCallback 훅을 사용한다.
useCallback 함수
useCallback()의 첫 번째 인수로는 재생성되지 않도록 막으려는 함수를 전달하고, 두 번째 인수로는 의존성 배열(Deps)을 전달하면 된다.
useCallback의 Deps는 useEffect나, React.memo의 Deps와 동일하다. 즉 다시 생성되기를 원하지 않을 때는 그냥 빈 배열로 놔두면 된다. Deps의 변경이 있으면 첫 번째 인수로 전달되는 함수를 재실행하고, 그게 아니라면 재실행하지 않는다.
[정리]
useMemo : 컴포넌트가 리렌더링 되더라도 특정 값이 변경되지 않으면 특정 연산 수행을 막음
React.memo : props가 변경되지 않으면 컴포넌트가 리렌더링 되지 않게 막음
useCallback : 컴포넌트가 리렌더링 되더라도 특정 값이 변경되지 않으면 함수를 다시 생성되지 않게 막음