본문 바로가기
source-code/React

리액트를 다루는 기술 _ 11장

by mattew4483 2021. 3. 16.
728x90
반응형

여기는 어디... 나는 누구...


11장. 컴포넌트 성능 최적화

지난번 열심히 만들었던 Todo List!

지음은 사용하는데 전혀 불편함이 없지만...

만약 추가할 데이터가 무수히 많아지면? 체감할 수 있을 정도로 지연이 발생하고 만다.

이를 방지하기 위해 컴포넌트 성능 최적화에 대해 알아볼 예정.

 

11-1) 많은 데이터 렌더링 하기

랙을 경험할 수 있을 정도로 많은 데이터를 렌더링해 보자!

 

App.js

기존 App 컴포넌트에 createBulkTodos 함수를 추가!

요 녀석의 return값이 usesState의 초깃값에 해당되게 된다!

(만약 useState(createBulkTodos())라고 적으면? App이 렌더링 될 때마다 createBulkTodos가 호출되고 만다!)

 

http://localhost:3000/

엄청난 스크롤의 향연.....

수정 및 삭제 기능도 무지막지하게 느려지고 말았다!

 

11-2) 크롬 개발자 도구를 통한 성능 모니터링

확실히 성능이 저하된 게 느껴지기는 하는데... 정확히 얼마큼?

→ 개발자 도구의 Performance 탭을 사용해 측정할 수 있다!

 

F12를 눌러 Performance 탭으로 들어가면...

녹화버튼을 누른 후, 측정하고 싶은 상황(이 경우 체크 버튼이 바뀔 때)을 시연한 후 작동되면 Stop!

우와...

그럼 이렇게 각 시간대 별 컴포넌트의 작업을 확인할 수 있다!

 

해당 작업이 처리되는 데 1.01초가 걸렸단다!

빠른 거 아닌가?? 고작(고작?!) 2500개의 데이터로 1.01초라면...

성능이 매우 나쁘다는 의미! 이를 최적화하는 방법을 알아보자.

 

11-3) 느려지는 원인 분석

가장 큰 이유는 쓸모없는 컴포넌트까지 렌더링 되기 때문!

 

컴포넌트는 다음과 같은 상황에서 리렌더링이 발생한다.

1. 자신이 전달받은 props가 변경될 때

2. 자신의 state가 바뀔 때

3. 부모 컴포넌트가 리렌더링 될 때

4. forceUpdate 함수가 실행될 때

 

그렇다면 우리는 왜?

현재 '할 일 1' 항목을 체크하면 App 컴포넌트의 state가 변경된다(App 컴포넌트 리렌더링)

부모 컴포넌트가 리렌더링 되었으니 TodoList 컴포넌트도 리렌더링!

따라서... 하위 수많은 컴포넌트(2~2500개의 todo)들도 리렌더링되고 만다! 으악!

 

물론 컴포넌트 개수가 많지 않다면 모든 컴포넌트를 리렌더링해도 느려지지 않지만...

(약 2000개가 넘어가면 성능이 저하된다고 한다)

 

따라서 리렌더링이 불필요할 때는 리렌더링을 방지해줘야 한다!

 

11-4) React.memo를 사용한 컴포넌트 성능 최적화

컴포넌트 리렌더링 방지 → 어디서 많이 들어봤는데...?

바로 React.memo함수!

어떻게 사용하는디? → 컴포넌트를 만든 후 감싸 주기만 하면 된다!

 

TodoListItem.js

이런 식으로!

이제 TodoListItem 컴포넌트는 todo, onRemove, onToggle이 바뀌지 않으면 리렌더링을 하지 않는다!

 

11-5) onToggle, onRemove 함수 바뀌지 않게 만들기

그런디... 이래도 Todo List의 반응은 영 늦다.

 

사실 당연한 거다! 

onRemove와 onToggle 함수는 배열 상태를 업데이트하는 과정에서 todos를 참고하기 때문에,

todos 배열이 바뀔 때마다 새로운 함수가 만들어진다!

(즉 React.memo를 안 쓴 것과 똑같다!)

 

이렇게 함수가 계속 만들어지는 상황을 방지하는 방법은

1. useState 함수형 업데이트 / 2. useReducer 사용

 

1. useState 함수형 업데이트

기존 App 컴포넌트를 봐보자.

App.js

onInsert 함수에서 setTodos 함수를 사용할 때는,

새로운 상태(여기서는 concat을 통해 반환된 새로운 list)를 파라미터로 넣어줬다.

 

그런데 이 대신, 상태 업데이트를 어떻게 할지 정의해 주는 업데이트 함수를 넣을 수도 있다!!!

(이를 함수형 업데이트라고 부른다)

 

이 방법을 적용해보면...

App.js

얼핏 보면 setTodos 함수에 todos => 만 추가된 것 같지만(사실 틀린 표현은 아니다)...

이를 통해 기존 todos.concat(todo)과 같은 특정 상태가 아닌,

상태를 이러이러하게 업데이트 해라고 알려주는 함수를 넣어 준 것!

이렇게 작성하면 useCallback의 두 번째 파라미터로 todos 배열을 넣지 않아도 된다!

 

쨔잔. 1.01초에서 0.94초로 획기적인 단축!

 

2, useReducer 사용하기

useState 함수형 업데이트 대신, useReducer를 사용해도 요 문제를 해결할 수 있다!

 

11-8) react-virtualized를 사용한 렌더링 최적화

하지만 우리의 Todo List에서 그 무엇보다 비효율적인 점은...

전체 2500개의 Todo 중 이용자는 9개만 보고 있는데도, 나머지 2491를 한번에 렌더링한다는 것!

 

따라서 react-virtualized를 사용해 리스트 컴포넌트에서

스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고, 크기만 차지하게끔 만들어주자.

 

1. 최적화 준비

우선 react-virtualied를 설치해주시고(엄청나게 오래 걸린다),

 

개발자 도구를 통해 list 속 각 항목의 크기를 확인한다.

왜? 내용은 렌더링 하지 않더라도, 크기는 차지해야 하니까! 차지할 크기를 미리 구한 것.

 

2. TodoList 수정

TodoList.js

우선 react-virtualized에서 List 란 컴포넌트를 import!

 

TodoList.js

요 List 컴포넌트를 사용하기 위해 rowRender란 함수를 작성해줬다.

요 함수는 List 컴포넌트에서 TodoItem을 렌더링할 때 사용되며,

이 함수를 List 컴포넌트의 props로 설정해줘야한다!

또한 파라미터로 index, key, style 값을 객체 타입으로 받아 와서 사용한다.

 

List 컴포넌트를 사용할 때는 

해당 List의 전체 크기와 각 항목의 높이,

각 항목을 렌더링할 때 사용해야 하는 함수, 배열 을 props로 넣어 줘야 한다.

→ 그럼 이 컴포넌트가 전달받은 props를 사용해 자동으로 최적화해 준다!

 

3. TodoListItem 수정

TodoListItem.js

TodoListItem도 다음과 같이 수정해줬다!

 

즉 기존에 보여 주던 내용을 div로 한 번 감싸고, 해당 div의 className과 props로 받아온 style을 적용!

 

TodoListItem.scss

즉 새로 만든 제일 가장자리 div(TodoListItem-virtualized)는 border와 짝수 div 색 다르게 적용하려고!

 

...??

이놈의 버전 문제...!

728x90
반응형