리액트 사용시 렌더링 성능 최적화가 필요한 이유
리액트는 웹브라우저의 렌더를 담당하는 DOM 트리의 렌더링 성능을 개선하기 위해 Virtual DOM (가상 돔) 을 이용한다. 하지만 복잡한 리액트 뷰를 생성할 때 최적화를 신경쓰지 않고 코드를 작성하면 메모리가 낭비되어 역시 렌더링 속도가 느려질 수 있다. 리액트를 활용하는 장점을 최대한으로 누리려면 성능개선에 신경을 쓰는 것이 좋다. 리액트에서는 메모리 낭비를 방지하기 위한 함수들도 제공하고 있어서, 이것들을 잘 사용하면 기본적인 렌더링 성능 관리가 가능하다.
웹 브라우저 엔진은 웹 표준 문서인 XML, HTML 문서를 파싱하여 문서의 각 항목들을 계층형태의 DOM 트리로 구축한다. 이 DOM 트리는 각 항목에 접근하여 수정하거나 삭제할 수 있는 인터페이스 역할을 한다. 그 다음에 렌더링 엔진에서 렌더트리가 구축되고, 렌더트리가 배치된 후, 렌더트리가 사용자 화면에 그려진다. 자바스크립트 로직에 의해 항목 중 한 군데라도 변화가 일어나면 DOM 의 모든 항목은 다시 그려진다. 당연히 문서 내에 렌더해야 하는 항목이 많아질 수록 그려지는 속도는 느려진다.
반면에 리액트는 DOM 을 추상화한 객체인 Virtual DOM 이라는 것을 일종의 사본처럼 미리 생성해두고, 항목에 변화가 생기면 전체 UI 를 리렌더한다. 그러나 새로 렌더된 뷰를 다 다시 그리는 게 아니라, DOM 과 비교 후 바뀐 부분만 실제 DOM에 적용하여 새로 그려지도록 한다. 그렇기 때문에 상대적으로 속도가 빠르다.
리액트의 Virtual DOM 은 항목에 하나라도 변경사항이 있으면 그 변경과 상관없는 요소들까지 한번 더 렌더가 된다. 이 과정에서 변수와 함수들도 재호출된다. 이는 불필요한 메모리 낭비이다. 리액트의 useMemo 와 useCallback 함수는 첫번째 인자로는 연산할 식, 두번째 인자로 의존성을 갖는 변수(디펜던시)를 넣도록 되어있다. 디펜던시가 바뀌면 첫번째 인자의 연산식을 호출하고, 디펜던시가 바뀌지 않았으면 기존의 값을 그대로 사용한다. 이렇게 하면 디펜던시의 최신값이 보장되면서 불필요한 함수 호출을 막을 수 있다.
https://gist.github.com/yeonwooz/c3056d5479736ba8469b083f035a7573
https://gist.github.com/yeonwooz/a1708a23e436c6ec1e4bf13741496a70
또한, 리액트에 부여하는 key 속성은 컴포넌트 인스턴스를 새로 갱신해주기 때문에 리렌더를 일으킨다. key 가 변경될 때 새로 그려지기 때문에 배열을 매핑할 때 변경가능성이 있는 index 를 key 로 지정하는 경우에는 주의해서 판단해야 한다. 고유한 id를 key 로 지정하면 의도하지 않은 리렌더가 발생할 우려를 하지 않아도 된다.
참고링크
- https://ko.reactjs.org/docs/faq-internals.html
- https://ko.reactjs.org/docs/reconciliation.html#keys
- https://cocoder16.tistory.com/36
- https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/