React 개인 프로젝트를 진행하며 성능 개선 작업을 하게 됐다.
그동안 작업한 개인 프로젝트들은 토이 개념이 강해 기능에 신경쓰고 성능에는 조금 무게를 덜었었는데
데이터 연동하며 좀 각잡고 규모 있게 진행하려다보니 성능 체크가 필수적이였다.
이전 팀 프로젝트에서 수행했었던 성능 개선 작업 목록을 참고하며 따라갔는데
그 중에서도 Lazy Loading에 대해서 기록해보려고 한다.
코드 스플리팅 (Code Splitting)
자바스크립트의 split 메서드가 스쳐 지나간다. 조각조각!
코드 분할. 하나로 크게 뭉쳐진 코드를 조각 조각으로 나누는 작업
그리고 나누어진 부분들 중 해당 부분이 필요한 시점에 동적으로 로딩하는 기술이다.
즉, 사용자가 필요한 코드만 비동기적으로 로딩하는 방법이다.
리액트 앱(SPA)에서 코드 스플리팅이 필요한 이유는?
리액트와 같은 SPA로 개발된 프로젝트는 빌드를 통해서 배포하는데
빌드된 내용을 살펴보면 하나의 JS 파일로 번들링되는 것을 확인할 수 있다.
이렇게 하나의 JS 파일로 번들링된 웹페이지에 진입하면
사용자는 최초 진입 시 모든 페이지에 대한 정보를 불러오게 되는데, 이는 초기 로딩을 느리게 만든다.
이렇게 사용자가 부정적인 경험을 얻도록 둘 순 없는데....😞
이런 초기 로딩이 느려지는 문제는 SPA로 개발된 CSR의 문제점 중 하나이기 때문에
SSR과 같은 해결 방법도 존재하지만! 조금 더 간단한 개선 방법인 코드 스플리팅도 존재한다.
우린 리액트에서 코드 스플리팅을 구현하기 위한 방법 중에서도 React.lazy에 대해 알아보려고 한다.
React.lazy
React.lazy는 동적으로 로딩되는 컴포넌트를 정의하기 위해 사용하는 함수다.
일반적으로 코드 스플리팅이 필요한 컴포넌트에서 적용한다.
React.lazy의 사용법을 보기 이전에
위에서 언급한 '모든 페이지에 대한 정보를 불러온다'는 구문을 다시 살펴보자.
만일 내 프로젝트의 페이지가 /, /profile, /search 와 같이 나뉘어져있다면?
사용자가 /profile 페이지에 들어가있는 동안 /, /search 페이지들의 정보는 사용자에게 필요하지 않을 확률이 높다.
그렇다면? 이러한 /, /profile, /search 를 분리하여 지금 사용자가 필요한 페이지에 대한 정보만 불러올 수 있도록 하면 성능 개선을 기대할 수 있다.
만일 이전에 App 컴포넌트에서 react-router를 이용해 페이지를 정의 했다면
페이지가 될 컴포넌트를 import 하는 구문의 모습은 아래와 같았을 것이다.
import Home from './routes/home.tsx';
const router = createBrowserRouter([
{
path: '/',
element: <Home />,
},
]);
function App() {
return (
<RouterProvider router={router} />
);
}
React.lazy를 적용하기 위해서는 import 구문을 다음과 같이 작성한다.
import { lazy } from 'react';
const Home = lazy(() => import('./routes/home.tsx'));
그리고 React.lazy가 적용된 컴포넌트는
React.Suspense 컴포넌트 안에 렌더링해야 한다.
import { lazy, Suspense } from 'react';
const Home = lazy(() => import('./routes/home.tsx'));
...
function App() {
return (
<Suspense>
<RouterProvider router={router} />
</Suspense>
);
}
React.Suspense는 fallback이라는 속성을 사용할 수 있는데
Suspense로 감싼 lazy 컴포넌트들이 로딩되는 동안 대신해 보여줄 콘텐츠를 설정할 수 있다.
import LoadingSpinner from './components/loading-spinner.tsx';
...
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<RouterProvider router={router} />
</Suspense>
);
}
여기에서 LoadingSpinner도 import되는 컴포넌트이지만
가볍고 fallback으로 이용되기 때문에 굳이 코드 스플리팅을 적용해주지 않고 직접 불러와서 사용하는 편이 낫다.
React.lazy를 적용한 코드 예시
위의 과정을 거쳐 내 프로젝트의 App 컴포넌트에 Lazy Loading을 적용했다.
react-router를 활용한 SPA이며
firebase를 활용하고 있어 로딩스피너를 노출하기 위한 조건이 추가로 필요해 전체 코드의 규모가 있으니 확인에 유념하길 바란다.
import { useEffect, useState, lazy, Suspense } from 'react';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { auth } from './firebase.ts';
import LoadingSpinner from './components/loading-spinner.tsx';
import * as S from './styles/global.ts';
const ProtectedRoute = lazy(() => import('./components/protected-route.tsx'));
const Home = lazy(() => import('./routes/home.tsx'));
const Profile = lazy(() => import('./routes/profile.tsx'));
const SearchResult = lazy(() => import('./routes/search-result.tsx'));
const Auth = lazy(() => import('./routes/auth.tsx'));
const Layout = lazy(() => import('./components/layout.tsx'));
const router = createBrowserRouter([
{
path: '/',
element: (
<ProtectedRoute>
<Layout />
</ProtectedRoute>
),
children: [
{
path: '',
element: <Home />,
},
{
path: '/profile',
element: <Profile />,
},
{
path: `/search/:searchKeyword`,
element: <SearchResult />,
},
],
},
{
path: '/auth',
element: <Auth />,
},
]);
function App() {
const [isLoading, setIsLoading] = useState(true);
const init = async () => {
await auth.authStateReady();
setIsLoading(false);
};
useEffect(() => {
init();
}, []);
return (
<>
<S.GlobalStyles />
<Suspense fallback={<LoadingSpinner />}>
{isLoading ? <LoadingSpinner /> : <RouterProvider router={router} />}
</Suspense>
</>
);
}
export default App;
React.lazy를 적용한 프로젝트
이로써 Lazy Loading을 적용한 내 프로젝트는 두개가 됐다!
누군가에게 참고가 될 수 있도록 첨부한다.
참고
리액트 공식 문서 (구버전) - Code Splitting : React.lazy
[React] React.lazy 코드 스플리팅으로 프로젝트 성능 개선하기
[React] 코드 스플리팅 (Code Splitting)
'REACT' 카테고리의 다른 글
[REACT] 프론트엔드의 테스트 코드 이해하기 (0) | 2024.07.01 |
---|---|
[FIREBASE][REACT] Firestore 데이터베이스 가져오기. getDoc과 onSnapshot의 차이는? (1) | 2023.12.30 |
[JAVASCRIPT][REACT] API 데이터 가져오기 (GET) (1) | 2023.11.26 |
[REACT] React Query(TanStack Query) 직접 적용해보며 알아가기 (0) | 2023.11.14 |
[REACT] React Hooks 탐구하기 (0) | 2023.10.15 |