React 렌더링 최적화
React 렌더링이란?
리액트에서 렌더링이란, 컴포넌트가 현재 props와 state의 상태에 기초하여 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업을 의미한다. 즉 사용자 화면에 View(내용)를 보여 주는 것.
초기 렌더링
렌더링을 담당하는 render 함수는 컴포넌트의 정보를 이용해 화면을 구성(렌더링)한다.
컴포넌트 내부에는 또 다른 컴포넌트들이 들어갈 수 있어, render 함수가 실행되면 그 내부에 있는 컴포넌트들도 재귀적으로 렌더링 된다.
렌더링이 끝나면 render 함수가 반환한 객체의 정보를 이용하여 문자열 형식의 html코드를 반환해 특정 DOM에 주입한다.
과정 설명
컴포넌트 정의
컴포넌트의 정보를 이용해 렌더링
문자열 형식 html 코드 반환
DOM 요소에 주입
리렌더링 (조화 과정)
리액트에서 뷰를 업테이트(리렌더링)할 때 “ 조화과정을 거친다 ” 라고 표현한다.
뷰를 변형시키는 것으로 보이는게 사실 render 함수로 새로운 요소로 갈아 끼워지는 것이기 때문이다.
이전에 생성한 컴포넌트 정보와 다시 렌더링한 정보를 비교해 최소한의 연산으로 DOM 트리를 업데이트한다.(Virtual DOM 개념)
과정 설명
데이터 업데이트
업데이트 된 데이터를 이용한 render 함수의 리렌더링
이전 컴포넌트와 리렌더링된 컴포넌트 차이 비교 (Virtual DOM)
바뀐 요소만 DOM에 반영
React 리렌더링 조건
자신의
state
변경부모에서 전달받은
props
변경부모 컴포넌트의 리렌더링
forceUpdate 함수가 실행될 때 (← 클래스형 컴포넌트에서 강제 리렌더링하는 방법)
React 렌더링 성능 최적화 방법
1. useMemo & useCallback
useMemo와 useCallback은 React Hook 중 하나. 메모이제이션으로 중복 연산 ****을 피할 수 있기 때문에 메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있다.
memoization: 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법
useMemo : 메모이제이션된 ’ 값 ‘을 반환한다.
useCallback: 메모이제이션된 ’ 함수 ‘를 반환한다.
2. React.memo()
컴퍼넌트를 렌더링하고 결과를 메모이제이션. 그리고 다음 렌더링이 일어날 때 props가 같다면, 메모이징된 내용을 재사용한다. props 혹은 props의 객체를 비교할 때 얕은 비교를 한다.
3. 자식 컴포넌트의 props로 객체를 넘겨줄 경우 변형하지말고 넘겨주기
// 생성자 함수
<Component prop={new Obj("x")} />
// 객체 리터럴
<Component prop={{property: "x"}} />
위와같이 props로 객체를 넘겨줄 경우 컴포넌트가 리렌더링 될 때마다 새로운 객체가 생성되어 자식 컴포넌트로 전달된다.
props로 전달한 객체가 동일한 값이어도 새로 생성된 객체는 이전 객체와 다른 참조 주소를 가진 객체이기 때문에 자식 컴포넌트는 메모이제이션이 되지않는다.
state를 그대로 하위컴포넌트에 넘겨주어 필요한 데이터 가공을 그 하위컴포넌트에서 해주는 것이 좋다.
4. 컴포넌트를 매핑할 때 key값으로 index 사용하지 않기
map함수를 돌릴 때 고유 key값을 부여해야하는데 이때 index로 key값을 설정한다면 중간에 새로운 값이 삽입된 경우 이후에 위치한 요소들은 전부 index가 변경된다.
이로 인해 key값이 변경되어 React는 key가 동일 할 경우, 동일한 DOM Element를 보여주기 때문에 예상치 못한 문제가 발생합니다. 또한, 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생한다.
다음과 같은 경우는 사용해도 괜찮다.
배열과 각 요소가 수정, 삭제, 추가 등의 기능이 없는 단순 렌더링만 담당하는 경우
id로 쓸만한 unique 값이 없을 경우
정렬 혹은 필터 요소가 없을 때
5. useState의 함수형 업데이트
기존의 useState를 사용하며, 대부분 setState시에 새로운 상태를 파라미터로 넣어주었다.
setState를 사용할 때 새로운 상태를 파라미터로 넣는 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있는데,
이렇게 하면 useCallback을 사용할 때 두 번째 파라미터로 넣는 배열에 값을 넣어주지 않아도 된다.
// 예시) 삭제 함수
const onRemove = useCallback(
(id) => {
setTodos(todos.filter((todo) => todo.id !== id));
},
[todos],
);
// 예시) 함수형 업데이트 후
const onRemove = useCallback((id) => {
setTodos((todos) => todos.filter((todo) => todo.id !== id));
}, []);
성능 최적화를 할 때 고려할 점
React 최적화 방식을 공부하면서 배웠던 내용중 하나는 아무때나 무분별한 사용은 지양하라는 것
useMemo
, useCallback
, React.memo
와 같은 것들을 성능을 걱정해서 미리 사용하는 것은 비추천
가독성에 좋지 않음
실행되는 모든 코드는 한줄한줄 비용이 소모된다. 따라서, 꼭 필요한 경우에만 사용
성능 이슈가 발생 했을 때, 해당 부분의 코드만 최적화를 추천
→ 실제로 Hello cocone에 useMemo
, useCallback
, React.memo를 적용했을 때 렌더링 시간이 크게 줄어들지 않고 비슷하거나 오히려 렌더링 시간이 늘어나기도 했다.
📌 이외에 추가로 적용할 수 있는 최적화 방법
레이지 로딩
폰트 최적화
이미지 최적화
코드 스플리팅
Subscribe to my newsletter
Read articles from Kayoung directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Kayoung
Kayoung
프론트엔드 개발자 김가영입니다. 개발과 일에 대한 생각을 주로 씁니다. https://velog.io/@kykim_dev/posts (2023 이전 블로깅)