React에 대한 여러 가지 생각


Youngrok Pak at 1 year, 7 months ago.

장난 삼아 써보던 React를 실무에 적용할까 말까 고민하면서 이것저것 좀 찾아봤다. 찾아보면서 알게 된 의외의 사실은 React의 핵심적인 장점이 성능에 있다는 것이다. React를 Angular랑 비교하면서 단방향이고 UI만 다루기 때문에 심플하다는 이야기를 많이 하지만, 그냥 옛날 방식의 템플릿에 비해 React가 뭐가 더 좋으냐고 물으면 성능 이외의 이유를 대기 어렵다. JSX 문법이 더 낫냐고 하면 그렇지도 않다. 진짜(?) 템플릿 언어들에 비해 JSX 문법이 가독성도 높다고 하기 어렵고 그 외 여러 가지 면에서도 쳐진다.

그럼 React가 왜 성능이 좋으냐. 결정적인 이유는 DOM Diff에 있다. 그냥 템플릿 엔진은 state가 바뀌면 전체 엘리먼트를 새로 그려야 하는데 React는 state가 변했을 때 Virtual DOM을 이용해서 DOM의 diff를 뜨고 그걸 이용해서 진짜 업데이트해야 하는 엘리먼트만 업데이트할 수 있다는 데 있다.  그리고 그 diff는 O(n^3)인 일반적인 tree diff와 달리 여러 가지 휴리스틱을 이용해서 O(n)으로 수행할 수 있기 때문에 매우 빠르다.

Virtual DOM을 쓰는 이유는 아마도 페이스북 개발자들이 DOM 엘리먼트를 직접 다루는 것보다 DOM을 흉내낸 Virtual DOM을 다루는 것이 훨씬 빠를 거라고 생각했기 때문일 것이다. JSX에서 <hr>이나 <input> 같은 태그도 굳이 닫아줘야 하는 이유도 기존 템플릿 엔진처럼 html을 생성한 다음 DOM 생성은 브라우저에 맡기는 방식이 아니라 JSX 문자열을 직접 파싱해서 Virtual DOM을 생성해야 하는데, 이 파서를 만들기 위해 html의 복잡한 규칙을 다 적용할 수 없었기 때문일 것이다. 그런데, 만약에 React의 tree diff가 빠른 결정적인 이유가 tree implementation의 가벼움 때문이 아니라 휴리스틱에 있다면 같은 알고리즘을 DOM 트리에 직접 적용할 수도 있지 않을까?

이런 생각을 하고 좀더 찾아보니 구글이 만든 Incremental Dom이라는 게 있었다. ReactDOM과 유사한 API를 이용해서 DOM을 만드는데, React와 달리 virtual dom을 만들지 않고 바로 DOM을 만드는 API다. 그리고 state가 바뀌면 실제 DOM과 새로 생성될 엘리먼트를 순차적으로 비교해서 DOM을 변경한다. Virtual DOM을 따로 보관하지 않으니 메모리 사용량도 적은 것은 당연한데, 의외로 성능에서도 많은 부분에서 React를 앞섰다. DOM 엘리먼트를 변경하는 일은 CSS를 다시 계산해야 하고 렌더링도 해야 하니 무거운 게 당연하지만, traverse하면서 diff만 뜨는 것은 Virtual DOM과 별 차이가 없지 않았을까? 구글의 개발자들은 Incremental DOM을 만든 이유를 템플릿 언어를 최대한 그대로 사용하면서 성능에서도 이득을 얻고 싶었기 때문이라고 설명하고 있다. 그래서 Incremental DOM API는 elementVoid 같은 API도 만들어두고 있는데 아마도 JSX에서 <br/>로 써야 하는 게 거슬렸던 게 아닐까.

여기까지 찾아보고 나서 당장 React를 버리고 Incremental DOM으로 가고 싶은 생각이 들었다. React에 비해 아무 단점이 없어보였고, JSX까지 쓸 수 있으니 다른 개발자들과 협업하는 프로젝트를 하더라도 손쉽게 양쪽으로 전환 가능하다. 그런데, 아직 아쉬움이 하나 남았다. Incremental DOM이 템플릿 언어를 지원하기 좀더 좋아서 기존 템플릿 문법을 그대로 쓰는 엔진을 만들 수 있는 건 알겠는데, 그렇다고 기존 템플릿 엔진을 그대로 쓸 수는 없다. 템플릿 엔진의 변환 결과가 html 코드로 나오는 것이 아니라 Incremental DOM API를 이용하는 자바스크립트 코드로 나와야 incremental하게 diff를 할 수 있기 때문이다.

여기서 의문이 하나 들었다. 만약에 그냥 html 코드로 DOM을 만들되 body에 attach는 하지 말고 DOMFragment로 갖고 있으면서 실제 DOM끼리 diff를 뜨고 업데이트를 한다면 성능이 어떨까? 물론 Incremental DOM에 비하면 메모리는 더 많이 쓰겠지만 Virtual DOM 트리를 항상 갖고 있어야 하는 React에 비하면 diff 하는 중의 메모리는 약간 더 많이 쓰지만 diff 끝나고 나면 비교했던 DOM은 날려도 되니 메모리 사용량이 더 줄어든다. 문제는 성능인데, JavaScript가 아니라 브라우저의 네이티브 코드가 HTML로 파싱하는 것이니 body에 attach하지 않고 DOM을 생성한다면 Virtual DOM과 별 차이 안 나거나 오히려 더 빠른 속도로 DOM을 생성할 수 있지 않을까? 일단 DOM 트리를 생성하고 나면 이후의 diff & update 속도는 별 차이 없을 테니 결과적으로 성능을 Incremental DOM이나 React에 비해 크게 떨어지지 않게 하면서 기존의 템플릿 엔진을 그대로 쓰는 활용법도 가능하지 않을까?

만약 이게 말이 된다면... 이라는 가정을 하고 좀더 검색을 해보니 나랑 비슷한 생각을 한 사람이 있었던 모양이다. Morphdom은 그냥 DOM끼리 비교해서 업데이트를 할 수 있게 해준다. 이걸 이용하면 Incremental DOM이나 ReactDOM 같은 API를 쓰는 코드로 트랜스파일하지 않고 그냥 html 코드를 생성해버리면 되기 때문에 구조도 약간 더 단순하게 만들 수 있고 기존의 템플릿 엔진을 그대로 쓸 수 있다. 성능에서 React에 비해 크게 떨어지지는 않는 모양인데, 좋은 벤치마크 결과를 못 찾았고, 아직 성숙도가 그리 높진 않은 듯 하다.

물론 세 가지 모두 최대한으로 최적화를 한다면 이론적으로 성능이 가장 앞설 가능성이 높은 것은 Incremental DOM이다. Virtual DOM은 상시 메모리 사용량이 가장 많고 Morphdom 방식은 diff 순간 메모리 사용량이 많다.

React가 Virtual DOM을 쓰기 때문에 생기는 장점이 하나 더 있다. 바로 React Native 같은 기술이 가능한 것이다. Incremental DOM은 실제 DOM으로 diff를 하고 업데이트를 하니까 React Native 같은 걸 만드려면 중간에 레이어를 하나 더 추가해야 한다. 그래도 가능하긴 하겠지만. 다만, 이게 현명한 선택인지는 모르겠다. React Native를 쓴다는 건 cordova를 이용한 하이브리드를 쓰지 않고 네이티브로 컴파일되는 방식을 쓰겠다는 것이고 그 이유는 성능일 것이다. 근데, 보통 네이티브에서는 데이터가 바뀌면 정확하게 관련 뷰만 변경하도록 하나하나 코드를 짜게 된다. 그러면 tree diff도 필요 없고, 휴리스틱으로 인해 불필요한 diff가 일어날 가능성도 없이 정말로 최적화된 업데이트만 일어난다. 과연 React Native를 썼을 때 이와 유사한 성능이 나올까? 어중간하게 하이브리드와 네이티브 사이 쯤의 성능이 나온다면 별로 의미가 없는 기술은 아닐까.

마찬가지 이유로 아주 복잡한 DOM을 편집해야 하는 애플리케이션의 경우에는 React 류의 기술이 애매할 것 같다. 예를 들어 WYSIWYG 에디터를 만드는데 React를 쓰는 게 의미가 있을까? 프리젠테이션이나 다이어그램을 만드는 툴에서 React를 쓴다면? 아무래도 이런 류의 애플리케이션은 네이티브 개발하듯이 변경 케이스별로 하나하나 짜지 않으면 좋은 성능을 내기 어렵지 않을까 싶다.

반대로, DOM의 분량이 작을 때도 React의 가치는 다소 퇴색한다. 요즘 브라우저의 innerHTML은 엄청나게 빠르다. 제법 긴 리스트를 무작정 처음부터 다시 업데이트해도 잘 돌아가기도 한다.

이것저것 많이 찾아보고 생각해봤지만 아직 어느 것도 최선에 가까이 도달했다는 생각은 들지 않는다. React 류의 기술이 기존 템플릿 엔진에 대비해서 의미가 있었던 이유가 성능이라는 점을 감안하면 Incremental DOM을 트랜스파일 타겟으로 쓰고 앞단에 JSX보다는 좀더 옛날 방식(?)의 템플릿 엔진을 붙이면 그럭저럭 만족할 수 있으려나? 아니면 현실적으로 대세를 따라 JSX+React를 쓰고 뒷단을 나중에 Incremental DOM으로 바꾼다든지.

 


Comments

  • 1 year, 7 months ago SeongWanPark
    리액트의 장점은 단방향 데이터 플로우입니다. 이를 통해 페이스북은 mvc 대신 flux/redux와 같은 스테이트 관리 패턴을 제안했죠,이 패턴들이 좋은건 ui 를 declarative 하게 작성함으로써 스테이트가 꼬이지 않고, 명료하게 나타난다는 겁니다.  단순 view layer가 아니라, 새 패턴의 한 부분이라 보는게 좋습니다. (약간 별개의 이야기지만 페이스북에선 draft.js 라는 리액트 기반 위지윅 에디터도 공개 중입니다)



Wiki at WikiNamu