리액트 컴포넌트 만들기, 그런데 렌더링 최적화를 곁들인

alsdnr0537·2020년 2월 12일
0
post-thumbnail

React 공식 문서 따르면,
리액트에서는 빠른 DOM 연산을 위한 여러 기술을 기본적으로 사용하고 있다고 합니다.

그럼에도 리액트에서 성능 최적화를 위한 몇가지 방법을 소개하고 있는데
그중 하나가 바로 불필요한 렌더링을 막는 것 입니다.
이번 포스팅에서는 이게 왜 필요한지, 어떻게 하는지 알아 볼까 합니다.

1. 기본 인풋 컴포넌트 만들어보자

불필요한 렌더링이 왜 필요한지 알아보기 위해
먼저 클래스형 컴포넌트로 기본적인 인풋을 만들겠습니다.
그리고 컴포넌트 렌더링이 어떻게 되는지를 보기 위해 로그를 찍어서 확인 해 볼 겁니다.

인풋에 기본적으로 필요한 props를 받는TextInput 컴포넌트를 만들었습니다. 그리고
render 함수 밑에 console.log()로 컴포넌트가 새로 렌더링 될 때 마다 로그를 찍도록 하겠습니다.


2. 인풋을 사용 해보자

이번엔 방금 만든 TextInput 컴포넌트를 사용하는 코드를 작성 해 보겠습니다.

위에서 만든 TextInput을 세개 렌더링하고 각각에게 state와 setState하는 이벤트를 전달해주는
MyForm 컴포넌트를 만들었습니다.


한번 실행 시켜 볼까요?

콘솔.png
첫 번째 인풋에 값을 입력하니, 세가지 인풋 모두 무수한 로그들이 찍히는걸 볼 수 있습니다.
로그가 찍혔다는 건 해당 컴포넌트가 새로 렌더링 되었다는 뜻이기 때문에
저희는 이걸 보고 생각 할 수 있습니다.
' 첫 번째 인풋에 값이 바뀌었는데 왜 모든 인풋이 새로 렌더링 되었지?, 두번째, 세번째 인풋은 쓸모 없는 렌더링 아닌가?'


3. 왜 이런 쓰잘데기없는 렌더링이 발생하는가?

이런 쓸모없는 렌더링이 생기는 이유에 대해 알기 위해서는 리액트에서 컴포넌트가 언제 새로 렌더링 되는지 알아야 합니다.

리액트가 리렌더링 하는 조건

  • 컴포넌트에 state가 변경되었을 때
  • 컴포넌트에 props가 변경되었을 때
  • forceUpdate()가 실행되었을 때
  • 부모 컴포넌트가 리렌더링 되었을 때

리액트는 위와 같은 조건으로 컴포넌트 리렌더링을 진행하는데,

저희가 작성한 예제에서 가만히 있는 TextInput들이 리렌더링 된 이유도
부모 컴포넌트 MyForm이 첫번째 TextInput입력에 의해 리렌더링 되어서
변경되지 않은 모든 컴포넌트들도 리렌더링이 되었던것 입니다.

이런 쓸모없는 리렌더링이 성능에 영향을 미치는데 컴포넌트 크기가 커질 수록 영향 또한 커집니다.
물론 위와 같이 몇 안되는 컴포넌트에 리렌더링은 리액트에 성능에 큰 영향을 미치지 않습니다.
하지만 컴포넌트가 커질 수록(MyFormTextInput들이 많아 질 수록)
렌더링 로직이 복잡해 질 수록 성능에 직접적으로 영향을 끼치기 시작 할 겁니다.


2020-01-26 03.37.39.gif
아주 극단적인 예제를 보면
입력하는 모습이 안보이다 한번에 값들이 들어가는 걸 확인 할 수 있습니다.
저렇게 많은 인풋들이 모두 불필요한 렌더링을 하게 되면, 모든 인풋들이 서로의 변경마다 모두 새로 레더링되어 성능저하를 일으키고, 렌더링이 버벅거리는 현상이 발생하게 됩니다.
그럼 이런 불필요한 렌더링은 어떻게 막을 수 있을까요?


4. 방금 만든 클래스형 컴포넌트를 최적화 해보자

4-1 PureComponent

방금 만든 TextInput을 변경해봅시다.

TextInput 컴포넌트가 원래는 React.Component를 상속 받았었는데 React.PureComponent로 변경하였습니다.
PureComponent는 리렌더링 될 때 props와 state를 얕은 비교를 통해 값이 변하지 않았는지 검사하고 변경되지 않았다면 새로 렌더링을 하지 않도록 제어하는 로직이 붙어있는 React.Component 입니다.

그럼 한번 다시 실행해보겠습니다.
콘솔
이번에는 처음에는 모든 TextInput이 로그가 찍히지만 그 후 입력 을 할 때에는 입력한 TextInput만 로그가 찍히게 되었습니다.
이 말은 즉, 입력한 컴포넌트 외에는 리렌더링이 되지 않았다는 뜻이기 때문에
불필요한 렌더링이 발생하지 않았다고 볼 수 있습니다!

이 예제에선 PureComponent로 아주 간단하게 최적화가 되어서 기쁘지만,
하지만 사실 PureComponent도 단점이 있습니다.
PureComponent는 얕은 비교를 통해서 렌더링을 제어 하기 때문에

Object나 Array, Function 같은 값이 props, state에 포함되어있으면 렌더링 제어가 제대로 되지 않습니다.
그럴 때 방법이 바로 아래에 shouldComponentUpdate를 구현 하는 것 입니다.
사실 PureComponent 또한 shouldComponentUpdate를 통해 구현 되어 있다고 합니다.

4-2 shouldComponentUpdate

방금 저희가 사용한 PureComponent는 사실 shouldComponentUpdate라는 함수를 구현하여 만들어진 컴포넌트 입니다.
이 함수는 return값으로 이 함수를 구현한 컴포넌트에 렌더링을 제어 할 수 있습니다.
다음 사진은 shouldComponentUpdate를 구현 했을 때에 렌더링 그래프입니다.
업데이트
(https://reactjs.org/docs/optimizing-performance.html)
사진을 보면 초록색은 true, 빨간색은 false 라는 뜻이고, 플래그에 뜻은 이렇습니다.
scu - shouldComponent에 리턴값
vDOMEq - React 렌더링 요소가 같은가?
원색깔 - 리렌더링 할 필요가 없는가?

사진에 초록색 원들이 불필요한 렌더링을 막은 컴포넌트라고 생각하시면 됩니다.
그럼 shouldComponentUpdate를 구현해 보겠습니다.

해당 코드는 현재 props가 nextProps(변경될 Props)와 다를 때만 컴포넌트가 업데이트 되지 않았다는 생각하라는 내용이고,
실행시키면 PureComponent 코드를 실행 시킨 것과 동일한 결과를 볼 수 있습니다.

이번에 최적화한 TextInput 같은 경우에는 변경을 비교하는 값이 String이기 때문에 shouldComponentUpdate를 쉽게 구현 할 수 있지만, Object, Array, Function, React Element 같은 깊게 비교해야 하는 값을 사용한다면 비교 할 값을 뜯어서 안에 내용을 비교해서 변경여부를 판단해야 할 수도 있습니다.

그럴 땐 Imuutable.js, react-past-compare 같은 라이브러리를 사용하면 편하게 비교 할 수 있는데,
해당 내용은 다음에 다루도록 하겠습니다.

지금 까지 클래스 컴포넌트를 렌더링을 제어 하는법을 알아봤습니다.
하지만 이세상엔 클래스형 컴포넌트만 있는게 아니죠.

이번엔 hooks를 사용한 함수형 컴포넌트에선 어떻게 렌더링 제어를 하는지 알아 볼 겁니다.
함수형 컴포넌트에서는 PureComponent, shouldComponentUpdate 사용 할 수없습니다.
그렇다면 함수형 컴포넌트에서는 어떻게 렌더링 제어를 할 수 있을까요?


5.이번엔 함수형으로 인풋을 만들어보자

위에서 만들었던 인풋 컴포넌트를 이번엔 함수형 컴포넌트로 만들었습니다.
물론 이번에 만든 함수형 컴포넌트도 MyForm에서 사용하면
불필요한 렌더링이 발생합니다. 따로 예제 코드는 작성하지 않겠습니다.


6. 함수형 컴포넌트도 최적화 해보자

함수형 컴포넌트에 경우 React.memo 함수를 통해 렌더링 제어가 가능합니다.
memo에 첫 인자는 최적화할 컴포넌트, 두번째 인자에는 현재 props,와 변경 될 props가 파라미터로 가지는 함수가 들어가고, 두번째 함수에 리턴에 따라 렌더링을 제어하게 됩니다.

위에 코드는 예전 value와 바뀐 value가 같으면 memo(memoization)하겠다는 뜻입니다.
즉 컴포넌트를 리렌더링 하지 않겠다는 거죠. (shouldComponentUpdate와 flag가 반대)

그리고 만약 두번째 인자를 넘기지 않으면 해당 컴포넌트가PureComponent가 됩니다.

정리

사실 모든 컴포넌트가 이렇게 최적화가 될 필요는 없습니다.
단지 필요한 경우에, 성능 이슈가 생겼을 때 이런식으로 최적화를 하면 된다는 것이죠.
하지만 컴포넌트를 설계를 할 때 부터 최적화를 고려하고, 어떻게 값을 비교 할지를 염두하면서 컴포넌트를 설계하면 분명 나중에 렌더링 이슈가 생기더라도 잘 대응 할 수 있을 겁니다.
긴 글 읽어 주셔서 감사합니다.

0개의 댓글