얼마 전까지만 해도 성능에 대한 고민 없이 react를 잘 사용하고 있었다. 초기에 react를 배울 때 shouldComponentUpdate를 잘 사용해야 한다는 글을 봤지만 크게 신경 쓰지 않았다. react-addons-perf라는 게 있다는 건 알았지만 점점 잊혀져갔다. 그러다 최근에 렌더링 성능 이슈가 발생했다. 슬라이드 바를 좌우로 드래그하면 화면에 있는 테이블의 데이터가 빠르게 업데이트되는 기능이었다. PC에서는 별다른 문제가 없었지만 출시된 지 2년이 넘은 안드로이드 폰이 문제였다.
에이~ react 느리네
순간적으로 그렇게 생각했다. 내 잘못이 아니라 react가 느린 거라고. 디버깅을 좀 더 해봤고, 오래지 않아 내 잘못이 하나씩 드러났다.
되도록이면 render 함수에서는 새로운 오브젝트를 생성하지 말자
아래 코드에서는 Component1의 numbers 속성에 하드 코딩된 배열을 넘기고 있다.
render() {
return <div>
<Component1 numbers={[1, 2, 3]} />
...
render함수가 호출될 때마다 numbers에는 매 번 새로운 배열이 할당된다. 메모리를 할당하는 시간이 느리다는 말을 하려는 게 아니다. 보다 더 큰 문제가 있다.
이쯤에서 shouldComponentUpdate를 살펴볼 필요가 있다. 이 함수는 nextState와 nextProps를 매개변수로 받아서 화면을 다시 그릴지 말지를 결정한다. 따로 구현하지 않았다면 무조건 화면을 다시 그린다. 물론 매 번 화면을 그려야 하는 컴포넌트도 존재하지만 일반적으로 PureComponent 정도는 사용해주는 게 좋다. 오브젝트의 1-depth 속성 값만 비교하는 방식으로 shouldComponentUpdate가 구현되어있다.
PureComponent를 사용했을 때 위 예제와 같이 numbers 속성으로 계속 새로운 배열을 넘겨준다면 어떻게 될까? 어렵지 않게 shouldComponentUpdate함수는 계속해서 true를 리턴한다는 사실을 알 수 있다. 결국 Component1의 render함수가 불필요하게 호출된다.
render함수에서 새로운 오브젝트를 생성하는 또 다른 예가 있다.
render() {
return <div>
<Component1 onClick={this.someFunc.bind(this))} />
...
bind를 호출하면 새로운 함수가 생성되고, numbers 예와 같은 문제를 야기한다. 이 경우 다양한 해결책이 존재하는데 그중 한 가지는 아래와 같다.
constructor(props) {
...
this.someFunc = this.someFunc.bind(this);
...
위 방식은 조금 귀찮기 때문에 아래의 방식을 추천한다
class App extends React.PureComponent {
constructor(props) {
}
someFunc = () => {
...
부득이한 경우 shouldComponentUpdate 함수를 직접 구현하자
props와 state의 1-depth 비교만으로는 부족한 경우가 존재한다. 때로는 n-depth까지의 모든 값을 비교해야 할 때도 있다. 그렇다고 하더라도 render 함수를 호출하는 것보다는 shouldComponentUpdate 함수에서 걸러주는 게 대개의 경우 훨씬 효율적이다. DOM element를 조작하는 비용이 상당히 크기 때문이다. 물론 render함수가 간단한 UI 구성을 갖고 있다면 굳이 shouldComponentUpdate 함수를 구현하지 않아도 좋다.
react-addons-perf
react에서 제공하는 성능 체크 툴이다. 어느 부분에서 rendering 시간이 오래 걸리는지 파악할 때 유용하다.
import Perf from 'react-addons-perf'
Perf.start()
...
Perf.stop()
const measurements = Perf.getLastMeasurements()
Perf.printInclusive(measurements)
Perf.printExclusive(measurements)
Perf.printWasted(measurements)
내 경우 슬라이드 바를 움직이기 전에 start를 호출했고, 슬라이드 바를 여러 번 움직인 후 stop을 호출했다.
Perf.printWasted는 낭비되는 시간을 알려주기 때문에 특히 유용하다. 만약 Perf.printWasted에서 시간 값이 크게 나온 컴포넌트는 위에서 설명한 팁을 적용해보자.
이 밖에도 페이스북에서 제안하는 성능 튜닝 방법도 고려해보자