티스토리 뷰

Web

[React] 렌더링 최적화

Tribal 2022. 4. 12. 10:56

서론

 

  리액트는 유저 인터페이스 구현에 유용한 프레임워크 라이브러리이다. JavaScript 라이브러리로 많이 사용되며, 최근에는 MS에서 만든 TypeScript도 지원한다고 하며, 웹과 어플리케이션 모두 개발할 수 있다. 왜 많이 사용할까?

 

  JavaScript 프레임워크 라이브러리로는 ReactJS 이외에도 AngularJS나 EmberJS 등 여러가지가 있다. 이러한 프레임워크 라이브러리들은 내부에서 동작하면서 유저 인터페이스 구현을 편하게 할 수 있도록 해준다는 점도 있지만, 유저에게 인터페이스를 제공하기 위해 브라우저가 렌더링하는 과정을 줄여 유연하게 만들어 준다는 점도 있다. 어플리케이션의 규모가 커질수록 발생하는 여러 문제들의 부담감을 줄여주기 때문에 많이 사용하게 된다. 그럼 어떻게 이게 가능한건지 알아보자.

 

  프레임워크 라이브러리들은 유저들에게 인터페이스를 제공하기 위해 MVC(Model View Controller) 또는 MVVM(View Model) 등 디자인 패턴을 사용하여 어플리케이션을 구조화해준다.

 

MVC(Model-View-Controller) 패턴

MVC 패턴 구성(Mozilla)

구성

  • Model : 변수, 상수와 같은 정보를 가진 데이터
  • View : 실제 UI를 보여주는 방식
  • Controller : 유저를 통해 발생한 이벤트 직접 처리자

  먼저 Model에서 데이터를 처리하고, 이를 View에 전달한다. View는 인터페이스를 통해 처리된 데이터를 사용자에게 보여준다.

  이후, 사용자가 특정 행위를 하면서 발생하는 이벤트는 Controller가 이를 받아 Model 또는 View에 이벤트가 발생했음으로 처리를 할 필요가 있다는걸 알려주고 다시 처음의 과정을 반복한다.

  여기서 실제 UI를 보여주기 위한 View 과정에서 브라우저는 렌더링 처리를 하게 된다.

 

MVVM(Model-View-ViewModel) 패턴

MVVM 패턴 구성(Microsoft)

구성

  • Model : 변수, 상수와 같은 정보를 가진 데이터
  • View : 실제 UI를 처리하고 보여주는 방식
  • ViewModel : 데이터를 보여주기 위한 속성 및 명령과 데이터 변경에 대한 이벤트 처리

  먼저 Model에서 데이터를 처리하고, 이를 View Model에 전달한다. View Model은 특정 UI 등을 표시할지를 결정해 이를 View에 전달한다. View는 View Model에서 전달받은 내용을 토대로 사용자에게 보여준다.

  이후, 사용자가 특정 행위를 하면서 발생하는 이벤트는 View Model에서 이를 받아 Model에서 처리할 데이터를 업데이트 해주거나 View에게 변경될 사항을 전달해주면서 과정을 반복한다.

  View Model은 Model에서 처리할 데이터를 알면서 View에게는 UI에 대한 변경 사항만을 전달하기 때문에 역시 여기서도 실제 UI를 보여주기 위한 View 과정에서 브라우저는 렌더링 처리를 하게 된다.

 

  둘의 공통점은 결국 Model에서 여러 데이터를 처리하고 View에서 이를 사용자에게 보여준다는 점이고, 차이점은 이벤트가 발생했을 때 이벤트를 받는 위치에서 직접 처리해서 전달하는지 다른 위치로 넘겨서 처리를 맡기는지 이다.

 

 

개요

 

  규모가 커질수록 이벤트 처리가 많아지기 때문에 View가 빈번히 발생하게 되고, 브라우저는 이를 매번 처리하기 위해 렌더링을 하게 된다. 그리고 렌더링의 과정이 상당히 복잡하기 때문에 여기에서 매번 상당한 성능을 잡아먹는다. 

 

  리액트는 이러한 문제점을 Virtual DOM을 사용하는 방식으로 해결했다. 이벤트가 발생했을 때 요청되는 여러 변화를 브라우저가 DOM을 렌더링하는 과정에 맡기는게 아니라 Virtual DOM에서 처리하여 이를 브라우저가 새로 렌더링을 하도록 하게 한다. 리액트는 이런 식으로 브라우저 렌더링에 대한 부담을 줄이는 특징 때문에 속도가 빠르다.

 

  리액트는 Virtual DOM 이외에도 컴포넌트(Component) 기반 구조라는 특징이 있다. 리액트는 모든 것을 컴포넌트라는 단위로 쪼개어 이를 조립해 개발하도록 만든다. 개발자가 여러 컴포넌트를 조합하여 개발하면, 리액트는 컴포넌트 단위로 렌더링을 수행한다.

 

  따라서 리액트가 컴포넌트 렌더링을 수행하는 시점을 줄일 수 있다면 더욱 성능 향상을 기대할 수 있다.

 

 

리액트가 컴포넌트 렌더링을 수행하는 시점

  • 하위 컴포넌트로 props를 전달하는 부분에서 props의 값이 변경되었을 때
1
<Comment key={id} content={content}>
cs
  • 변경된 state 값을 사용할 때 (현재 컴포넌트에서 사용하는 데이터)
1
2
3
4
5
6
7
class Comment extends React.Component {
    state= {
        uuid: 0,
        name'',
        content''
    }
};
cs
  • forceUpdate()를 실행하였을 때 (강제 렌더링)
  • 위의 요인으로 부모 컴포넌트가 렌더링되었을 때

  forceUpdate()를 제외하곤 리액트 자체에서 컴포넌트가 변경된걸 확인하고 자체적으로 렌더링을 수행한다. 렌더링 최적화를 하기 위해선 이 부분들을 잘 기억해야 한다.

  Props는 부모 컴포넌트가 데이터를 하위 컴포넌트로 전달하는 부분에서, 하위 컴포넌트는 해당 값이 변경되었는지 확인하고 렌더링 여부를 결정한다. props가 객체 형태의 값이라면, 해당 객체 형태(배열, 함수 포함)의 값이 동일한지 평가를 수행한다.

  State는 컴포넌트 내부에서 데이터를 state에 저장하여 사용하는데, state가 변경되면 해당 state를 사용하는 부분에서 이를 확인하고 렌더링 여부을 수행한다. 

  부모 컴포넌트가 어떤 요인으로 렌더링된 경우, 하위 컴포넌트는 props, state의 변경 유무와 상관없이 무조건 렌더링을 수행한다. 컴포넌트 설계와 관련된 부분이다.

 

 

리액트 렌더링 최적화 방법

 

설계 시점

 

  리액트는 데이터의 흐름이 단방향이기 때문에 사전에 사용할 데이터를 분류하여 구현할 여러 컴포넌트를 설계한다. 데이터는 컴포넌트 내부에서 state로 저장된다. 리액트의 컴포넌트 렌더링 특성상 컴포넌트에 불필요한 state를 저장하는건 여러 하위 컴포넌트 렌더링에까지 영향이 갈 수 있다. 따라서 데이터를 잘 분류해 컴포넌트를 설계할 필요가 있다.

  • 함께 필요가 있는 데이터만 객체 형태로 묶어 객체 형태의 state를 최대한 분할
  • state는 해당 state를 사용하는 컴포넌트의 최상단 컴포넌트에 선언

 

구현 시점

 

  최적화의 기본은 특정 시점에 불필요한 코드를 실행하지 않는 것이고, 변화가 없는 상황에서 이전에 연산된 결과가 있다면 이걸 활용하는게 불필요한 행동을 줄일 수 있다. 이전에 연산된 결과를 저장해서 동일한 연산 수행 과정을 제거하는 기술을 메모이제이션이라고 부른다.

  예전에는 리액트로 개발을 할 때 클래스형 컴포넌트가 여러가지 제공되는게 많았기 때문에 클래스형 컴포넌트를 사용했지만, 요즘은 리액트 Hook이 지원되고 나서 함수형 컴포넌트로 많이 바뀐 추세이다. 그러다보니 클래스형 컴포넌트에서 렌더링 최적화를 할 때 컴포넌트가 변경되었는지를 확인하는데 사용하던 shouldComponentUpdate() 이벤트 메소드를 사용할 수 없게 되었다. 대신 리액트 hook을 이용하여 React.memo와 같은 메모이제이션을 해줘야 한다.

  • 고정된 상수로 된 데이터는 const 타입으로 렌더링 이전 단계에서 생성하여 사용
  • 불필요한 연산을 가져올 수 있는 리터럴로 된 객체 등은 사전에 따로 props로 생성해두고 처리
  • 하위 컴포넌트에 전달되는 props가 객체인 경우, 불필요한 객체 생성을 막기 위해 state 자체로 전달하여 처리
  • 메모이제이션 활용
    • React.memo : 함수형 컴포넌트 전체를 렌더링 제어할 때 사용
      (컴포넌트간 매핑 key 값은 반드시 고유 값으로 할 것)
    • React.useMemo : 함수형 컴포넌트 내부 특정 연산의 이전 수행한 값을 저장해두기 위해 사용
      (수행된 값을 반환)
    • React.useCallback : 함수형 컴포넌트 내부 이전 특정 연산 자체를 저장해두기 위해 사용
      (함수 자체를 반환)

 

참고

- MVC 패턴 : https://developer.mozilla.org/ko/docs/Glossary/MVC

- MVVM 패턴 : https://docs.microsoft.com/ko-kr/xamarin/xamarin-forms/enterprise-application-patterns/mvvm

- 리액트의 특징 : https://medium.com/hivelab-dev/react-js-tutorial-part1-c632e34fc32

- 리액트 렌더링 이해 및 최적화 : https://medium.com/vingle-tech-blog/react-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-f255d6569849

- 리액트 렌더링 최적화 8가지 방법 : https://cocoder16.tistory.com/36

 

'Web' 카테고리의 다른 글

[React] NextJS를 사용한 SSR 처리  (0) 2022.05.11
[React] react-router 다루기  (0) 2022.04.20
[React] test code  (0) 2022.04.01
[selenium] Selenium GET/POST method 코드 예제  (1) 2020.07.07
[Python] requests 모듈  (0) 2018.10.02
댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31