UI 응답성을 개선하기 위한 성능 최적화 기법에는 Web Worker과 Debounce, Throttle가 있다.
적용 대상과 역할이 조금 다르기 때문에 이 글을 읽고, 상황에 맞게 적용해 성능을 개선해보자!
1. Web Worker (웹 워커)
무거운 연산이나 백그라운드 작업을 별도의 스레드에서 수행하는 기법을 말한다.
JS는 싱글 스레드라 무거운 연산이 메인 스레드를 막아 UI 렌더링이 멈출 수가 있기 때문에
웹 워커는 별도의 스레드에서 작업 수행시켜 메인 스레드의 부하를 줄여 UI의 렌더링이 끊기지 않도록 유지하기 위해 사용한다.
1.1 사용 예시
// App.js
function heavyCalculation(n) {
let result = 0;
for (let i = 0; i < n * 1_000_000_00; i++) {
result += i;
}
return result;
}
function App() {
const handleClick = () => {
const result = heavyCalculation(10);
console.log('결과:', result);
};
return <button onClick={handleClick}>계산하기</button>;
}
export default App;
위와 같이 메인 스레드에서 무거운 연산을 수행하면
heavyCalculation이 실행되는 동안 UI가 멈추거나 버튼이 잘 안 눌리는 현상이 발생한다.
// worker.js
self.onmessage = function (e) {
const n = e.data;
let result = 0;
for (let i = 0; i < n * 1_000_000_00; i++) {
result += i;
}
// 계산 결과를 메인 스레드로 전송
self.postMessage(result);
};
// App.js
import React from 'react';
function App() {
// Worker를 생성
const worker = new Worker(new URL('./worker.js', import.meta.url));
const handleClick = () => { // 워커에 메시지(데이터) 전송
worker.postMessage(10);
};
worker.onmessage = (e) => { // 워커에서 계산이 끝나면 결과 받기
console.log('웹 워커 계산 결과:', e.data);
};
return <button onClick={handleClick}>계산하기</button>;
}
export default App;
위의 코드처럼 수정하면 Web Worker로 heavyCalculation은 백그라운드에서 연산하므로 UI가 멈추지 않는다.
(Webpack/Vite 기준) import worker from './worker.js?worker' 과 같은 문법을 사용하기도 한다.
2. Debounce
사용자가 입력할 때마다 API 호출 또는 이벤트가 발생하면 너무 많은 호출이 발생하게 되어 서버 부하가 증가하고 UI 렉이 발생하게 된다.
이를 사용자가 입력을 멈추고 일정 시간이 지난 후에 한 번만 실행하도록 수정하면 불필요한 중복 호출을 방지할 수 있다.
Debounce는 주로 검색창, 입력 이벤트, 자동완성 등에 사용하며, 마지막 이벤트 이후 일정 시간이 지나야 실행되도록 하여 해결한다.
2.1 사용 예시
import React, { useState } from 'react';
function SearchBox() {
const [query, setQuery] = useState('');
// ❌ 매 타이핑마다 API 호출 발생
const handleChange = async (e) => {
const value = e.target.value;
setQuery(value);
// 바로 API 호출
fetch(`https://api.example.com/search?q=${value}`)
.then(/* ... */);
};
return <input type="text" onChange={handleChange} value={query} />;
}
export default SearchBox;
사용자가 연속으로 타이핑하면, 매 글자마다 API를 호출해서 중복 요청이 많아진다.
이렇게 하면 서버/클라이언트에 부하가 크고, 화면도 잦은 re-render로 느려질 수 있다.
import React, { useState, useCallback } from 'react';
import debounce from 'lodash/debounce';
function SearchBox() {
const [query, setQuery] = useState('');
const searchAPI = (value) => {
fetch(`https://api.example.com/search?q=${value}`)
.then(/* ... */);
};
// useCallback에 debounce 래핑
const debouncedSearch = useCallback(
debounce((value) => {
searchAPI(value);
}, 300),
[]
);
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
// 입력이 멈추고 300ms 지나면 실제 호출
debouncedSearch(value);
};
return <input type="text" onChange={handleChange} value={query} />;
}
export default SearchBox;
이렇게 수정하면 사용자가 0.3초 이내에 계속 입력할 시, API 호출이 지연되고 입력이 멈추었을 때 한 번만 API 호출한다.
3. Throttle
스크롤, 마우스 이동, 리사이즈 같은 이벤트는 매우 자주 발생한다.
이러한 이벤트가 과도하게 호출되면, UI 렌더링과 연산에 부하가 크기 때문에
Throttle을 통해 주로 스크롤, resize, 마우스 이동 등에서
이벤트가 여러 번 발생해도 일정 시간 단위로 한 번만 이벤트를 처리하도록 하여 전체 호출 횟수를 제한하여 해결한다.
3.1 사용 예시
import React, { useEffect } from 'react';
function ScrollLogger() {
useEffect(() => {
const handleScroll = () => {
console.log('스크롤 위치:', window.scrollY);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div style={{ height: '2000px' }}>스크롤 해보세요!</div>;
}
export default ScrollLogger;
스크롤 이벤트는 초당 수십~수백 번 이상 발생할 수 있으며, 콘솔 로그가 지나치게 자주 찍히고 성능도 떨어진다.
import React, { useEffect } from 'react';
import throttle from 'lodash/throttle';
function ScrollLogger() {
useEffect(() => {
const handleScroll = throttle(() => {
console.log('스크롤 위치:', window.scrollY);
}, 100); // 100ms마다 최대 1회만 실행
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div style={{ height: '2000px' }}>스크롤 해보세요!</div>;
}
export default ScrollLogger;
스크롤 이벤트가 많아도, 0.1초에 한 번만 로그가 찍히며 화면 렉이나 불필요한 이벤트 중복 처리가 크게 줄어든다.
초기 로딩 속도가 느리다면?
[React] 초기 로딩 속도가 느리다면? 성능 최적화!!
React 환경에서 성능을 개선하는 방법은 다양하게 있다.그 중 번들 크기를 감소시켜 초기 로딩 속도를 최적화 하는 방법 몇 가지를 자세하게 알아보자. 1. Tree Shaking사용되지 않는(dead) 코드를 제
mi-dairy.tistory.com
'프론트엔드 > React' 카테고리의 다른 글
[React] 서버에서 상태를 가져온다고? React-Query (0) | 2025.03.30 |
---|---|
[React] Zustand쓰면 Redux 이제 못 씀, Zustand 파헤치기 (0) | 2025.03.29 |
[React] 초기 로딩 속도가 느리다면? 성능 최적화!! (1) | 2025.03.28 |
[React] DOM?? Virtual DOM?? (1) | 2025.03.25 |
[React] 라이프사이클(Lifecycle)에 대해서 (5) | 2024.09.12 |