대규모 애플리케이션에서는 모든 컴포넌트를 한 번에 불러오면 초기 로딩 속도가 느려집니다. defineAsyncComponent를 활용한 코드 스플리팅 기법과 v-memo, shallowRef 등을 이용한 렌더링 최적화 전략을 다룹니다.
우리가 웹 브라우저로 사이트에 접속할 때, 브라우저는 자바스크립트 파일을 다운로드합니다. 모든 컴포넌트가 하나의 거대한 app.js 파일에 뭉쳐 있다면(Bundle), 사용자는 당장 필요하지 않은 관리자용 페이지나 무거운 차트 라이브러리까지 모두 다운로드될 때까지 하얀 화면만 보게 됩니다. defineAsyncComponent는 이런 거대한 덩어리를 잘게 쪼개어(Code Splitting), "필요한 순간에만 해당 파일을 받아오게" 만드는 훌륭한 최적화 기술입니다.
| 방식 | 코드 작성법 | 특징 및 사용처 |
|---|---|---|
| 정적 Import (동기) | import Header from './Header.vue' | 앱이 시작될 때 무조건 로딩. 헤더, 푸터, 메인 네비게이션 등 즉시 렌더링되어야 하는 필수 요소에 사용. |
| 동적 Import (비동기) | defineAsyncComponent(() => import('./Heavy.vue')) | 해당 컴포넌트가 화면에 나타날 때(v-if 등) 다운로드. 모달(Modal), 복잡한 차트, 에디터, 잘 안 가는 라우터 페이지에 사용. |
단순한 버튼 하나, 작은 텍스트 블록까지 비동기 컴포넌트로 만들면, 브라우저가 수백 개의 자잘한 파일을 다운로드하기 위해 수백 번의 네트워크 요청을 보내야 하므로 오히려 성능이 심각하게 저하됩니다. 비동기 컴포넌트는 오직 '크기가 크고, 초기 화면에 필요하지 않은 덩어리'를 격리할 때만 사용하는 것이 올바른 최적화 전략입니다.
우리가 웹 브라우저로 사이트에 접속할 때, 브라우저는 자바스크립트 파일을 다운로드합니다. 모든 컴포넌트가 하나의 거대한 app.js 파일에 뭉쳐 있다면(Bundle), 사용자는 당장 필요하지 않은 관리자용 페이지나 무거운 차트 라이브러리까지 모두 다운로드될 때까지 하얀 화면만 보게 됩니다. defineAsyncComponent는 이런 거대한 덩어리를 잘게 쪼개어(Code Splitting), "필요한 순간에만 해당 파일을 받아오게" 만드는 훌륭한 최적화 기술입니다.
| 방식 | 코드 작성법 | 특징 및 사용처 |
|---|---|---|
| 정적 Import (동기) | import Header from './Header.vue' | 앱이 시작될 때 무조건 로딩. 헤더, 푸터, 메인 네비게이션 등 즉시 렌더링되어야 하는 필수 요소에 사용. |
| 동적 Import (비동기) | defineAsyncComponent(() => import('./Heavy.vue')) | 해당 컴포넌트가 화면에 나타날 때(v-if 등) 다운로드. 모달(Modal), 복잡한 차트, 에디터, 잘 안 가는 라우터 페이지에 사용. |
단순한 버튼 하나, 작은 텍스트 블록까지 비동기 컴포넌트로 만들면, 브라우저가 수백 개의 자잘한 파일을 다운로드하기 위해 수백 번의 네트워크 요청을 보내야 하므로 오히려 성능이 심각하게 저하됩니다. 비동기 컴포넌트는 오직 '크기가 크고, 초기 화면에 필요하지 않은 덩어리'를 격리할 때만 사용하는 것이 올바른 최적화 전략입니다.
<!-- HIDDEN_TAB -->
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp, ref, defineAsyncComponent } = Vue;
// 서버에서 아주 무거운 파일을 받아오는 상황을 시뮬레이션
const loadHeavyChart = () => {
return new Promise((resolve) => {
console.log("네트워크: 대용량 컴포넌트 다운로드 시작... (2초 소요)");
setTimeout(() => {
console.log("네트워크: 다운로드 완료!");
resolve({
template: '<div class="chart-box">📊 대규모 데이터 차트 렌더링 완료! (15MB)</div>'
});
}, 2000);
});
};
const AsyncHeavyChart = defineAsyncComponent({
loader: loadHeavyChart,
// 로딩 중일 때 보여줄 임시 컴포넌트
loadingComponent: {
template: '<div class="loading-box">⏳ 열심히 다운로드 중입니다... 잠시만 기다려주세요!</div>'
},
delay: 200 // 0.2초 이상 지연될 때만 로딩 컴포넌트 표시
});
const App = {
components: { AsyncHeavyChart },
template: `
<div class="card">
<h2>성능 최적화: 비동기 컴포넌트</h2>
<p style="color: #94a3b8; margin-bottom: 20px;">
버튼을 누르기 전까지는 무거운 차트 컴포넌트를 로드하지 않습니다.<br/>
(F12 개발자 도구의 Network 탭 효과를 콘솔에서 확인하세요!)
</p>
<button class="btn" @click="showChart = true" v-if="!showChart">
무거운 차트 컴포넌트 로드하기
</button>
<!-- 사용자가 버튼을 클릭하면 그때서야 비동기 컴포넌트가 마운트(다운로드)를 시작합니다. -->
<AsyncHeavyChart v-if="showChart" />
</div>
`,
setup() {
const showChart = ref(false);
return { showChart };
}
};
createApp(App).mount('#app');
</script>