React 렌더링 최적화를 위한 대표적인 방법
React는 컴포넌트라는 단위를 기반으로 설계하고, 데이터가 변경됨에 따라 적절한 컴포넌트만 효율적으로 갱신하고 렌더링한다.
이러한 React의 렌더링을 더 효율적으로 할 수 있게 도와주는 Hook들이 있다.
우선, 컴포넌트가 재랜더되는 조건을 살펴보자.
- 부모에서 전달받은 props가 변경될때
- 부모 컴포넌트가 리렌더링 될 때
- 자신의 state가 변경 될 때
- shouldComponentUpdate에서 true가 반환될 때
- forceUpdate가 실행될 때
컴포넌트가 복잡해질수록 위 조건의 극히 일부를 포함하고 있지만 재랜더하지 않아도 될 컴포넌트들 역시 재랜더가 되곤 한다. 그래서 대표적으로 아래 3가지를 통해 최적화한다.
1. useMemo
useMemo(()=> func, [input_dependency])
이는 함수를 캐싱하기 위해 사용되며, 함수에서 리턴되는 값을 메모라이징한다.
가령, getAverage라는 평균을 구하는 함수가 있다고 가정하자.
이 함수를 포함하는 컴포넌트가 리렌더링 될 때마다 이 연산을 수행하게 된다.
만약, 평균값을 구하는 연산이 엄청 오래 걸리는데, 이 함수의 변경 감지값이 아닌 다른 요인으로 인해 재랜더링 되는 상황이라면 필요 이상의 시간을 소모하는 꼴이 된다.
이에 따라
func: 캐시하고 싶은 함수
input_dependency: useMemo가 캐시할 func에 대한 입력 배열로, 해당 값들이 변경될 때만 func이 호출된다.
이렇게 되면, 종속 변수들(input dependency)가 변하지 않으면 함수를 굳이 다시 호출하지 않아도 된다.
함수 호출 시간도 세이브할 수 있고, 같은 값을 props로 받는 하위 컴포넌트의 리렌더링도 방지할 수 있다.
2. React.memo 컴포넌트 (메모이제이션)
클래스형 컴포넌트에서는 shouldComponentUpdate를 제공하는데 이는, setState를 동작 시켜 state를 변경시켜도
이전 값과 같을 수가 있다. 이러한 이전 값을 기억하여 재랜더를 방지하기 위해 최적화할 수 있는 메서드이다.
shouldComponentUpdate(prevProps, prevState) {
return this.props.data !== prevProps.data;
}
이 메서드를 활용하면, 이전의 Props와 현재의 Props가 변경된 부분이 다를 경우에만 true 반환하여 해당 컴포넌트만 렌더링되게 할 수 있다.
하지만 함수형 컴포넌트에서는 shouldComponentUpdate를 사용할 수 없는데 리액트 공식 문서에서는 그 대안으로 React.memo를 제시하고 있다.
기능은 shouldComponentUpdate와 같이 컴포넌트의 props가 바뀌지 않았다면 리렌더링 하지 않도록 할 수 있는 기능이다.
3. useCallback
useMemo는 종속 변수의 변화를 감지하여 함수 연산을 다시 수행할지 말지를 결정하여 이 전 함수의 리턴 값을 기억한다면, useCallback은 함수 선언을 캐싱하는데 사용된다.
함수는 객체이고, 새로 생성된 함수는 다른 참조값을 가진다.
만약, 어떠한 함수를 props로 받는 상황을 가정해보면, 2번의 memo를 쓴다고 하여도
함수가 새로 생성 및 선언되면 함수의 내부 기능이 동일하다 하여도 다른 주소값을 가지기 때문에 props가 변한 것으로 인지하여 재렌더를 하게 된다.
이럴 때, useCallback을 쓴다.
useCallback으로 함수를 선언해주면, 종속 변수들이 변하지 않는 이상 굳이 함수를 재생성 및 선언하지 않는다.
이전에 있던 참조 변수를 그대로 하위 컴포넌트에 props에 전달하여 하위 컴포넌트도 props가 변경되지 않았다고 인지하여 하위컴포넌트의 리렌더링을 방지할 수 있다.