나는 현재 jQuery 기반 프레임워크를 사용해 프론트엔드 개발을 하고 있다.
팀과 프로젝트의 특성상 오랜 기간 이 방식에 익숙해져 있었고, 별다른 불편 없이 개발을 이어왔다.
하지만 웹 프론트엔드의 흐름은 이미 SPA(Single Page Application) 중심으로 옮겨갔고, 그 중심에는 React가 있다.
개인적으로 토이 프로젝트를 하거나 새로운 UI를 시도할 때는 자연스럽게 React를 선택하게 된다.
최근에는 Next.js를 공부하면서 React 생태계에 대한 이해도 점차 넓어지고 있다.
그래서 이번 글에서는 SPA의 대표 예로 React를, MPA의 예로 jQuery를 비교해 보려 한다.
React의 개념적인 장점들은 이제 익숙하다. 예를 들어,
- 가상 DOM을 통한 렌더링 효율 향상
- 상태 관리를 기반으로 한 일관된 컴포넌트 업데이트
- 선언적인 방식으로 UI를 구성할 수 있다는 점 등
하지만 여전히 마음 한켠에는 질문이 남아 있었다.
“실무에서도 정말 React가 jQuery보다 나은 선택일까?”
“React의 기술적 이점이 사용자 경험 측면에서도 체감될 수 있을까?”
실제로 내가 작성한 jQuery 기반 코드와 React 코드의 차이는 대부분 개발자 입장에서의 차이지, 사용자 입장에서는 큰 차이가 없어 보일 수도 있다.
그래서 직접 실험해 보기로 했다.
jQuery 방식과 React 방식으로 동일한 기능을 구현한 뒤, 실제 성능과 개발 경험을 비교해 보는 것.
실험 주제는 “실시간 코인 시세 테이블”.
WebSocket으로 데이터를 받아 빠르게 갱신되는 UI를 jQuery와 React 두 방식으로 구현하면서 어떤 차이가 있었는지를 정리해 보려 한다.
🧩 jQuery 기반 웹 개발이 어떻게 이루어졌는가?
jQuery는 한때 웹 프론트엔드 개발의 표준처럼 여겨졌던 라이브러리였다. $('#element').text('변경') 와 같은 직관적인 문법으로 DOM 요소를 쉽게 조작할 수 있었고, 간단한 애니메이션, Ajax 요청 처리, 이벤트 바인딩까지 대부분의 작업을 코드 몇 줄로 구현할 수 있었다.
하지만 jQuery 방식은 DOM 조작을 개발자가 직접 수행해야 한다는 점이 가장 큰 특징이다. 특정 요소의 텍스트를 바꾸거나 클래스를 추가하는 것부터 시작해, 화면의 특정 부분을 다시 그리고 상태에 따라 조건부 렌더링을 처리하는 것까지 모두 수동으로 작성해야 한다. 이러한 방식은 초기에는 빠르고 직관적일 수 있지만, 화면이 점점 동적으로 복잡해지고 데이터 흐름이 많아질수록 한계를 드러낸다.
특히, 상태(state)가 UI에 직접적으로 영향을 주는 구조에서는 jQuery 방식이 점점 버거워진다. 하나의 상태가 여러 요소에 영향을 줄 경우, 모든 요소를 수동으로 찾아가 각각 업데이트해야 한다. 이로 인해 코드가 쉽게 꼬이고, 상태와 UI가 불일치하는 상황도 자주 발생한다. 유지보수는 점점 어려워지고, 새로운 기능을 추가하거나 버그를 수정하는 데도 많은 시간과 비용이 들어간다.
이러한 복잡함은 특히 SPA(Single Page Application)처럼 한 페이지 안에서 다수의 컴포넌트와 복잡한 상태 변화가 일어나는 구조에서 크게 드러난다. jQuery는 기본적으로 MPA(Multi Page Application) 중심의 개발을 전제로 하기 때문에, 클라이언트 사이드 라우팅이나 컴포넌트 구조, 선언형 UI 같은 개념이 없다. 결과적으로 jQuery를 사용해 SPA 형태의 애플리케이션을 구현하려면 많은 것을 직접 구현하거나 억지로 짜 맞춰야 하며, 그 과정에서 구조적 한계와 비효율이 생기기 마련이다.
실제로 jQuery로 개발된 프로젝트에서는, 숙련도가 부족한 개발자일수록 데이터와 UI 컴포넌트가 따로 노는 현상이 자주 발생하는 걸 볼 수 있었다. 사실 나 역시 처음에는 똑같은 실수를 반복하곤 했다.
왜냐하면 사람은 완벽하지 않기 때문이다.
“데이터를 받았으니, 이 데이터를 사용하는 모든 컴포넌트에 잘 반영해야지”라고 머릿속으로는 생각하지만, 막상 구현하다 보면 꼭 한두 군데는 빠뜨리기 마련이다.
그리고 그 누락된 부분은 나중에야 발견되고, 그때마다 디버깅을 반복하게 된다.
🧠 리액트는 어떤 철학으로 UI를 다루는가?
리액트는 기존의 DOM 중심 개발 방식과는 조금 다른, ‘선언적(declarative)’ 방식의 UI 개발 철학을 가지고 있다.
전통적인 jQuery 방식에서는 “어떤 DOM을 어떻게 바꿀지”를 직접 명령하는 식이었다면, 리액트는 “지금 상태에 따라 UI가 어떻게 보여야 하는지를 선언”하면, 그 나머지는 리액트가 알아서 처리한다. 즉, UI를 직접 조작하는 게 아니라, 상태만 관리하면 UI는 그에 따라 자동으로 바뀐다.
이런 철학은 컴포넌트 기반 아키텍처와 자연스럽게 연결된다. 리액트에서는 화면을 작은 단위의 컴포넌트로 나누고, 각각이 자신의 상태(state)와 속성(props)을 기반으로 독립적으로 동작한다. 이 덕분에 재사용성이 높고, 유지보수가 쉬운 코드를 만들 수 있다.
또한 리액트는 Virtual DOM 이라는 개념을 활용한다. 실제 DOM을 직접 조작하지 않고, 메모리 상에서 가상의 DOM 트리를 만들어 변경사항을 계산한 뒤, 실제로 필요한 부분만 브라우저에 반영한다. 이 과정이 매우 빠르게 이루어지기 때문에, 많은 양의 데이터가 바뀌는 경우에도 성능 저하 없이 렌더링 할 수 있다.
무엇보다 큰 장점은 상태(state)와 렌더링을 분리해준다는 점이다. 복잡한 UI일수록 상태와 화면이 꼬이기 쉬운데, 리액트는 상태가 바뀌면 자동으로 관련 UI만 다시 렌더링 되도록 구조화되어 있다. 개발자는 “언제, 어디서, 무엇을 업데이트해야 하지?” 같은 고민을 덜고, 오직 **‘현재 상태가 이렇다면 화면은 이렇게 보여야 한다’**는 로직에만 집중하면 된다.
이러한 개념들이 바로 리액트를 사용하는 핵심 이유이기도 하다.
자, 이제 본격적으로 실험을 시작해보자.
먼저 간단한 실습으로, Upbit의 API를 통해 코인 종목들의 실시간 시세를 가져오는 작업부터 진행할 것이다.
🚀 실험시작!
이번 실험에서는 간단한 Node.js 서버를 구축해, 실시간 코인 시세를 클라이언트로 전송하는 구조를 만들었다. 서버는 Upbit API를 통해 데이터를 받아오며, 클라이언트는 이를 WebSocket으로 수신해 화면에 표시한다.
테스트 대상 코인은 총 10 종목이다.
비트코인, 이더리움, 리플, 에이다, 솔라나, 도지코인, 트론, 이더리움 클래식, 비트코인 캐시, 코스모스
메이저 알트 위주로 구성했다.
*하지만 시세가 불규칙하게 들어오는 점, 실험 조건을 동일하게 맞추기 위해서는 동일한 데이터와 동일한 시간 간격차로 들어와야 되기 때문에 가짜 시세 데이터를 뿌려주는 코드를 개발해서 사용했다. 초당 10회~1000회의 종목 시세를 랜덤 하게 뿌려준다.
우선 jQuery 기반으로 진행한다.
기능은 매우 단순하다:
- 시작 버튼: 서버로부터 시세 데이터를 받아오기 시작한다.
- 멈춤 버튼: WebSocket 연결을 닫는다.
- 테이블: 실시간으로 수신한 코인 시세를 화면에 표시한다.
DOM 조작은 모두 jQuery로 처리하며, 테이블의 각 행은 종목별로 구성된다. 새로운 시세가 들어오면 해당 행의 값을 경신하도록 구현할 예정이다.
이제, React로 동일한 기능을 구현해 보았다. Vite로 리액트 프로젝트를 생성했고, Typescript를 사용했다.
React.memo 와 useMemo를 사용해 컴포넌트 렌더링 최적화에 신경을 써주었다. 왜냐하면 새로운 코인 시세가 들어올 때마다 테이블 전체를 리렌더링 하면 비효율적이기 때문이다.
더 극단적인 비교를 위해서 동일한 테이블을 복사해 총 N개의 테이블을 화면에 띄워놓고 여러 관점에서 성능을 비교해 보겠다.
🧪 실험 구성
항목 | SPA 방식(React) | MPA 방식(jQuery) |
UI 구성 | Table Component를 N개 렌더링 | jQuery로 DOM 테이블 요소 N개 생성 |
데이터 수신 | WebSocket (초당 10회) | |
갱신 방법 | setState → Virtual DOM → DOM diffing | DOM 직접 .html(), .text() 갱신 |
화면 구조 | #root 내에 N개 컴포넌트 반복 | <div class="table-wrapper"> 내에 N개테이블 수동 렌더링 |
측정 시간 | 1m |
🧪 실험 결과
테이블 개수 : 1
항목 | SPA 방식(React) | MPA 방식(jQuery) |
LCP* | 0.18초 | 0.07초 |
CLS* | 0.00 | 0.0 |
INP* | 40ms | 32ms |
GPU Memory | 3.9MB ~ 5.5MB | 1.2MB |
Heap Memory | 7.5MB ~ 15.6MB | 1.5MB ~ 5.3MB |
렌더링 | 96ms | 124ms |
페인팅 | 44ms | 50ms |
fps | 75fps | 75fps |
테이블 개수 : 20
항목 | SPA 방식(React) | MPA 방식(jQuery) |
LCP* |
0.18초 | 0.04초 |
CLS* | 0.16 | 0.0 |
INP* | 40ms | 32ms |
GPU Memory | 5.5MB ~ 10.6MB | 3.6MB~4.8MB |
Heap Memory | 11.7MB ~ 27.8MB | 2.2MB ~ 2.7MB |
렌더링 | 191ms | 742ms |
페인팅 | 67ms | 186ms |
fps | 75fps | 75fps |
*LCP(Largest Contentful Paint) : 사용자에게 보여주는 가장 큰 영역의 콘텐츠가 얼마나 빨리 나타나는지를 측정한다. 페이지 로딩이 다 되었다는 인상에 가장 큰 영향을 주기 때문에 지표로 삼는 것이다.
*CLS(Cumulative Layout Shift) : 레이아웃 쉬프트 현상이 얼마나 일어나는지를 측정한다. 네트워크 요청의 시간 차이로 인해서 중간 콘텐츠가 뒤늦게 끼어들어 레이아웃이 껑충 뛰는 현상이 나타나면 사용자 경험에 좋지 않다.
*INP(Interation to Next Paint) : 사용자가 클릭 등의 액션을 했을 때 UI의 변화가 생기기까지의 시간을 말한다.
📊 React vs jQuery: 실험을 통한 웹 성능 비교
이번 실험은 동일한 UI를 React와 jQuery 두 방식으로 구현한 뒤, 실제 브라우저 환경에서 성능 차이를 비교한 결과를 정리한 글이다. 특히 LCP(Largest Contentful Paint), GPU/Heap 메모리 사용량, 렌더링 및 페인팅 처리 시간 등 실제 사용자 경험에 영향을 줄 수 있는 항목들을 중심으로 측정했다.
⏱ LCP: React가 더 느리다
LCP(Largest Contentful Paint) 값은 React가 jQuery보다 눈에 띄게 높았다.
물론 사용자 입장에서는 0.1초 차이는 체감하기 어려울 수 있지만, 성능 지표상 React가 주요 콘텐츠를 늦게 그리는 구조임은 분명하다.
그 이유는 React가 SPA(Single Page Application)이기 때문이다.
즉, 초기 로딩 시 index.html만 받아오고, 실제 콘텐츠는 JS 번들을 받은 후 실행되고 나서야 DOM에 그려진다.
이 과정이 LCP를 지연시키는 주된 원인이다.
반면, jQuery 기반 MPA는 HTML 자체가 서버에서 완성되어 전달되기 때문에 브라우저는 즉시 콘텐츠를 렌더링 할 수 있다.
✅ 해결 방법? → SSR(Server Side Rendering), 예를 들면 Next.js를 사용하면 React 앱도 초기 LCP를 크게 개선할 수 있다.
※ 참고: 이번 실험에서는 CLS(Cumulative Layout Shift), INP(Interaction to Next Paint)는 실험 항목에서 제외했다.
💾 메모리 사용량: React가 더 많다
Heap Memory와 GPU Memory 모두에서 React가 더 많은 메모리를 사용하는 것으로 나타났다.
이는 React의 구조적인 특성 때문이며, 다음과 같은 이유로 정당화된다:
- React는 Virtual DOM을 메모리에 유지하며 상태 변화 감지를 위한 추가 연산 구조(Fiber Tree 등)를 갖고 있다.
- 컴포넌트 기반 구조는 각각의 상태, 클로저, 렌더링 정보 등을 메모리에 보존한다.
- 렌더링 최적화를 위해 GPU 친화적인 스타일(transform, opacity, will-change)을 자주 활용, 브라우저가 해당 요소를 GPU 레이어로 분리(Composite Layer) 하게 유도한다.
이는 단점이 아니라 성능 최적화를 위한 메모리 사용이라는 관점으로 보는 것이 타당하다.
반면, jQuery는 구조화 없이 DOM을 직접 조작하며, 상태 저장 없이 동작하기 때문에 메모리 사용량이 훨씬 낮다.
⚡ 렌더링과 페인팅 속도: React가 더 빠르다
흥미롭게도 렌더링(Rendering)과 페인팅(Painting) 시간은 React가 더 짧았다.
이는 React가 가지고 있는 렌더링 최적화 메커니즘 덕분이다:
✅ Virtual DOM의 효율적인 변경 감지
변경된 상태를 Virtual DOM과 비교(diffing)해 실제 DOM에 필요한 부분만 업데이트한다.
✅ Batch Update 처리
여러 setState() 호출을 한 번에 묶어 처리함으로써 렌더링 요청을 줄이고, 렌더링 부하를 낮춘다.
✅ GPU 친화적 스타일 사용
React는 transform, opacity 같은 속성을 활용해 Reflow 없이 페인팅할 수 있는 구조를 만들어낸다.
✅ 컴포넌트 단위 렌더링 분리
전체가 아니라 필요한 컴포넌트만 리렌더링 되므로,되므로, 전체 UI의 페인팅 비용이 줄어든다.
📌 정리: React는 “한 번 그릴 때 빠르지만”, “그리기까지 시간이 걸린다”
React는 렌더링 최적화가 잘 된 구조다.
한 번 그릴 때는 매우 빠르지만, 그 준비 과정(JS 실행, 상태 초기화, 컴포넌트 트리 생성 등)이 있기 때문에
최초 렌더링까지 걸리는 시간, 즉 LCP는 더 느릴 수 있다.
🧪 추가실험
실제 시세는 무작위적으로 내려온다. 초당 10회, 즉 0.1초에 시세 데이터 하나만을 내려주지 않는다. 무수히 쏟아질 때는 클라이언트에서 감당이 어려운 수준으로 쏟아지기도 한다. 그렇기 때문에 특정 시간마다 렌더링을 하도록 배치 업데이트 방식으로 해결하기도 한다. 그런데 과연 어느 정도 수준까지 가야 아, 리액트가 렌더링 측면에서 확실히 좋구나라고 느낄지가 궁금했다.
시세를 초당 1000회 내려주고 테이블의 개수는 40개까지 늘려보았다. 이랬더니 jQuery로 개발한 화면이 FPS가 확연하게 떨어지면서 프레임드롭이 발생하고 화면이 버벅거리기 시작했다.
반면 React로 개발한 화면은 테이블의 개수가 40개가 되어도 FPS가 안정적이게 유지되었고 버벅거림도 없었다.
jQuery처럼 FPS가 떨어지기 시작한 건 80개를 넘어설 때쯤이었다.
🧾 총평: 렌더링 성능 측면에서의 React 우위
이번 실험은 실시간 시세 데이터를 수신하고 이를 테이블 형태(예: 호가창)로 뿌려주는 상황을 가정해,
React와 jQuery의 렌더링 성능을 직접 비교해 보는 것이 목적이었다.
실험 결과, 기본적인 렌더링 속도와 페인팅 처리에서는 React가 더 나은 효율을 보였고,
테이블 수가 많아지고, 시세 데이터가 초당 1000건 이상 급격히 들어오는 고부하 환경에서도
React가 더 안정적으로 프레임을 유지하며 버벅거림 없이 동작하는 것을 확인할 수 있었다.
이는 React가 갖고 있는 Virtual DOM, 배치 업데이트, 컴포넌트 단위 렌더링 구조 등
내부적인 렌더링 최적화 메커니즘이 실제 브라우저 환경에서도 효과적임을 보여주는 근거라 할 수 있다.
물론 상태 관리나 유지보수성, 확장성 등에서도 React는 jQuery에 비해 많은 장점을 갖고 있지만,
이번 실험은 어디까지나 렌더링 관점에 한정된 비교였다.
결론적으로, 실시간 데이터와 복잡한 UI가 함께 존재하는 웹 환경에서는 React가 렌더링 성능 측면에서 확실한 우위를 가진다는 점을 이번 실험을 통해 확인할 수 있었다.
실험에 사용된 코드는 링크를 참고하길 바란다.
https://github.com/Shinsieon/react_vs_jquery_test
GitHub - Shinsieon/react_vs_jquery_test: SPA vs MPA: A Rendering Perspective with React and jQuery
SPA vs MPA: A Rendering Perspective with React and jQuery - Shinsieon/react_vs_jquery_test
github.com
'개발 프로젝트' 카테고리의 다른 글
<하루정리> 끝. (0) | 2025.08.01 |
---|---|
IOS 앱 출시 <하루정리> - 개발 기록 (0) | 2025.03.15 |
Nodejs 메모리 leak 현상 해결하기 (0) | 2025.01.02 |
[PyQt5] 자동 배포 프로그램 (2) | 2024.01.03 |
[PyQt5] 성경 프롬프터 프로그램 (85) | 2023.11.29 |