현재 오즈코딩스쿨 강의를 통해 프론트엔드를 학습하고 있습니다.
본 포스트는 해당 강의에 대한 내용 정리를 목적으로 합니다.
컴포넌트를 바로 불러오지 않고, 실제로 그 컴포넌트가 필요할 때 불러옴
React.lazy는 컴포넌트를 동적으로 import하여 코드 스플리팅을 구현할 수 있게 해주는 함수이다. 즉, 해당 컴포넌트를 실제로 필요할 때까지 로딩하지 않고, 나중에 필요한 시점에서 가져오도록 할 수 있다.

const MyComponent = React.lazy(() => import('./MyComponent'));
이렇게 하면 MyComponent는 실제로 렌더링되기 전까지 로드되지 않으며, 이를 통해 초기 번들 크기를 줄일 수 있다.
컴포넌트를 불러오는 동안 사용자에게 보여줄 임시 화면 설정.
Suspense는 React.lazy로 로드되는 컴포넌트가 비동기적으로 로드되는 동안 보여줄 UI를 정의하는 컴포넌트이다. 일반적으로 로딩 상태를 보여주는 데 사용된다.

import React, { Suspense } from 'react';
const MyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}
위 코드에서 MyComponent가 로드되는 동안에는 "Loading..."이라는 메시지가 보여지게 된다. 이 방식은 사용자에게 더 나은 경험을 제공할 수 있게 해준다.
코드 스플리팅을 통한 성능 향상
초기 번들 크기를 줄여서 첫 로딩 속도를 개선할 수 있다. 필요한 컴포넌트만 로드되기 때문에 사용자 경험이 향상된다.
간단한 문법
React.lazy와 Suspense만으로 쉽게 코드 스플리팅을 적용할 수 있어 설정이 간편하다.
지연 로딩(Deferred loading)
사용자가 실제로 해당 컴포넌트를 보기 전까지는 로딩하지 않기 때문에 리소스 낭비를 줄일 수 있다.
로딩 UI 설정 가능
Suspense의 fallback 속성을 통해 사용자에게 자연스러운 로딩 상태를 보여줄 수 있다.
서버사이드 렌더링(SSR) 미지원
React.lazy는 기본적으로 클라이언트 사이드에서만 동작하므로, Next.js 등 SSR 환경에서는 문제가 발생할 수 있다.
에러 핸들링 한계
로드 중에 에러가 발생해도 직접적인 오류 처리가 어려우며, ErrorBoundary를 함께 사용해야 한다.
초기 로딩이 아닌 중간 로딩이 생길 수 있음
라우팅을 통해 페이지를 전환하거나 특정 기능을 사용할 때 로딩이 생겨 사용자 흐름이 끊길 수 있다.
Suspense의 제한적 활용
React 18 이전까지는 Suspense가 데이터 로딩 등 비동기 처리에 제한적이었고, 아직 모든 비동기 상황에 완벽하게 대응하지는 못한다.

번들링이란 여러 개의 자바스크립트 파일과 모듈들을 하나의 파일로 묶어주는 작업이다. 이를 통해 웹 페이지가 서버로부터 로드해야 할 파일 수를 줄이고, 초기 로딩 속도를 향상시킬 수 있다.
파일 수 감소
여러 개의 JS, CSS, 이미지 파일 등을 하나로 묶음으로써 브라우저가 서버에 요청해야 하는 파일 수를 줄일 수 있다. 이로 인해 HTTP 요청 횟수가 감소하여 네트워크 성능이 향상된다.
로딩 속도 개선
번들된 파일은 압축(예: gzip, Brotli)되어 전달되므로, 전송 크기 감소를 통해 페이지 로딩 속도를 높일 수 있다.
의존성 관리 용이
번들링 도구(Webpack, Vite 등)는 모듈 간 의존성을 분석해 필요한 코드만 포함시켜 주므로, 불필요한 코드 제거(Tree shaking)와 의존성 자동 정리에 효과적이다.
캐싱 효율화
번들 파일은 보통 해시값을 파일명에 포함하므로, 코드 변경이 없으면 브라우저가 캐시를 재사용할 수 있어 성능이 향상된다.
코드 일관성 유지
개발 환경에서 모듈을 나눠 작성하더라도, 번들링을 통해 일관된 하나의 실행 파일로 만들어지므로 배포와 실행 환경이 안정적이 된다.

Rollup : 라이브러리 번들링에 적합한 경량 번들러이다.
장점
단점
Webpack : 가장 널리 사용되는 JavaScript 번들러이다.
장점
단점
코드 스플리팅이란 애플리케이션의 코드를 여러 개의 청크(chunk)로 나누어 필요한 시점에 필요한 코드만 로드할 수 있도록 하는 기술이다. 이를 통해 초기 로딩 속도를 더욱 개선하고, 사용자 경험을 향상시킬 수 있다.

초기 로딩 속도 개선
사용자 경험 향상
트래픽 절감
한 번에 모든 리소스를 다운로드하지 않기 때문에, 모바일이나 저속 네트워크 환경에서도 데이터 낭비를 줄일 수 있다.
Lazy Loading과의 연계 가능
코드 스플리팅은 React의 React.lazy와 함께 사용되며, 라우터와 연계하면 페이지 단위, 컴포넌트 단위로 분할이 가능하다. 이를 통해 라우팅 기반 분할(예: 페이지별 코드 스플리팅)도 자연스럽게 구현할 수 있다.
기능 단위 최적화
대형 애플리케이션에서 기능별로 코드 분리를 하면, 유지보수성이 향상되고, 버그 발생 범위도 좁힐 수 있다.

React.lazy()는 import를 지연시켜 해당 컴포넌트가 실제로 렌더링될 때 로드되도록 한다.// About.js 라는 컴포넌트를 동적으로 불러온다
const About = React.lazy(() => import('./About'));
React.lazy로 불러오는 컴포넌트는 비동기 처리이므로, 로딩 중에 보여줄 화면을 Suspense로 감싸고 fallback 속성으로 지정한다.import React, { Suspense } from 'react';
const About = React.lazy(() => import('./About'));
function App() {
return (
<div>
<h1>Welcome</h1>
<Suspense fallback={<div>로딩 중...</div>}>
<About />
</Suspense>
</div>
);
}
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { Suspense } from 'react';
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>페이지 불러오는 중...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
에러 핸들링: 컴포넌트 로딩 실패 시를 대비해 ErrorBoundary를 함께 사용하면 좋다.
분리 기준: 페이지 단위 외에도 특정 기능이나 무거운 UI 컴포넌트 단위로 분리할 수 있다.


debounce는 너무 자주 실행되는 함수 호출을 제어하여, 일정 시간 동안 입력이 없을 때 한 번만 실행되도록 만드는 기술이다.
주로 검색창 자동완성, 스크롤 이벤트, resize 이벤트 등에서 성능 최적화를 위해 사용된다.
입력이 계속 들어오면 실행을 미루다가, 마지막 입력 이후 일정 시간이 지나야 실제로 실행된다.
debounce 기본 사용법 (lodash)
먼저 lodash 라이브러리를 설치한다: npm install lodash
import React, { useState, useMemo } from 'react';
import debounce from 'lodash/debounce';
function SearchBox() {
const [value, setValue] = useState('');
// 디바운스 함수는 useMemo로 한번만 생성
const debouncedSearch = useMemo(() =>
debounce((input) => {
console.log('API 요청:', input); // API 호출 위치
}, 300)
, []);
const handleChange = (e) => {
const input = e.target.value;
setValue(input);
debouncedSearch(input); // debounce된 함수 호출
};
return (
<input
type="text"
placeholder="검색어 입력"
value={value}
onChange={handleChange}
/>
);
}
useMemo로 debounce 함수는 재생성되지 않도록 처리debounce 직접 구현해서 사용하기
function debounce(func, delay) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), delay);
};
}
const handleResize = debounce(() => {
console.log('창 크기 변경됨');
}, 500);
window.addEventListener('resize', handleResize);
throttle은 특정 시간 간격마다 함수가 한 번만 실행되도록 제한하는 기술이다.
→ 주기적으로 반복되는 이벤트(스크롤, resize 등)의 실행 횟수를 강제 제한해서 성능을 높인다.
npm install lodashimport React, { useEffect } from 'react';
import throttle from 'lodash/throttle';
function ScrollTracker() {
useEffect(() => {
const handleScroll = throttle(() => {
console.log('스크롤 위치:', window.scrollY);
}, 500); // 0.5초에 한 번만 실행됨
window.addEventListener('scroll', handleScroll);
// cleanup
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div style={{ height: '2000px' }}>스크롤 해보세요</div>;
}
여기선 스크롤 이벤트가 자주 발생하더라도 0.5초에 한 번만 로그가 찍힌다.
throttle 직접 함수 구현해서 사용하기
function throttle(func, delay) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
const throttledResize = throttle(() => {
console.log('윈도우 리사이즈!');
}, 1000);
window.addEventListener('resize', throttledResize);
| 구분 | debounce (디바운스) | throttle (스로틀) |
|---|---|---|
| 정의 | 마지막 이벤트 발생 후 일정 시간 지나면 실행 | 일정 시간마다 한 번만 실행 |
| 주요 목적 | 잦은 입력 중 마지막 것만 반영 | 잦은 입력 중 주기적으로 반영 |
| 실행 타이밍 | 입력 멈춘 뒤 delay만큼 기다렸다가 실행 | 입력 중이라도 delay마다 한 번씩 실행 |
| 대표 사용 예 | 검색 자동완성, 폼 입력값 검증, API 요청 | 스크롤 추적, 윈도우 리사이즈, 마우스 이동 추적 |
| 실행 횟수 제어 | 무조건 1회만 (입력 멈출 때) | 여러 번 실행 가능하지만 간격 조절됨 |