여러분은 컵라면에 물 붓고 3분을 얌전히 기다리시나요? 저는 한국인이라서인지(?) 3분이 되기도 전에 몇번이나 열어보곤 하는데요. 🤣
기다리는 것을 별로 좋아하지 않는 것은 만국 공통인것같습니다.
유저의 50% 이상이 웹페이지를 로드하는데 3초 이상 걸리는 경우, 기다리지 않고 웹사이트를 이탈한다하는데요.
웹사이트의 속도를 저하시키는 원인은 무엇이 있을까요? 크게는 파일의 크기, 서버 요청 수, 페이지의 어떤 요소들이 나타나는 순서에 따른 이유 등으로 얘기 할 수 있는데요.
속도 향상을 위해 우리가 할 수 있는 방법은 어떤 것이 있을까요?
사용자가 애플리케이션을 로드할 때 초기 경로에 필요한 코드만 보내도록 JavaScript 번들 분할
⇒ JS번들 파일의 크기를 줄여 초기 로딩 시간 단축, 필요한 코드만 로드해서 UX 향상
동적 임포트(Dynamic Import)를 사용하여 구현 가능. 이를 통해 애플리케이션의 여러 부분을 나누어 개별적인 청크(chunk)로 분할 가능. 이렇게 분할된 청크는 필요한 시점에 로드 됨.
JavaScript 번들 파일을 분할하려면, 주로 웹팩(Webpack)과 같은 모듈 번들러를 사용합니다. 웹팩은 코드 분할을 지원하는 여러 방법을 제공합니다.
예를 들어, 웹팩에서 동적 임포트를 사용하여 코드를 분할하는 방법은 다음과 같습니다:
import()
함수 사용:
import()
함수 사용. (필요한 모듈 동적으로 가져옴)import("./module")
.then((module) => {
// 모듈을 사용합니다.
})
.catch((error) => {
// 에러 처리를 합니다.
});
웹팩 설정 파일에서 설정하기:
webpack.config.js
)에서 코드 분할을 설정 가능 optimization.splitChunks
속성을 사용하여 청크 분할 방법을 지정module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'all',
},
},
};
동적 임포트를 사용한 코드 분할:
const handleClick = async () => {
const module = await import("./module");
// 모듈을 사용합니다.
};
주요 Assets을 미리 로드하여 로딩속도를 향상시키려면 알아야 할 것은 2가지!
<link>
태그를 사용하여 프리로딩 가능. 이미지를 프리로딩하는 경우:
<link rel="preload" href="path/to/image.jpg" as="image">
스크립트 파일을 프리로딩하는 경우:
<link rel="preload" href="path/to/script.js" as="script">
스타일시트를 프리로딩하는 경우:
<link rel="preload" href="path/to/style.css" as="style">
HTML의 <link>
태그를 사용하여 프리페칭 가능.
다음 페이지에서 사용할 스크립트 파일을 프리페칭하는 경우 :
<link rel="prefetch" href="path/to/script.js">
이미지를 프리페칭하는 경우:
<link rel="prefetch" href="path/to/image.jpg">
스타일시트를 프리페칭하는 경우:
<link rel="prefetch" href="path/to/style.css">
속도 향상을 위해 우리가 할 수 있는 방법으로 2가지를 살펴봤는데,
이제 React 에서 랜더링 엔진 개선과 UX 향상을 위해 React 18 에 내놓은 주요 기능을 살피는 시간을 가져보자!
React 16.6에서 실험적인 기능이었는데, React 18에서 FINALLY 정식 기능으로 출시 !! 🥳🥳🥳
컴포넌트를 읽어야 하는데 데이터가 아직 준비가 되지 않았다고 리액트에게 알려주는 새로운 매커니즘. 데이터 로딩, 코드 분할, 서버 사이드 렌더링 등 다양한 비동기 작업을 더욱 간편하게 처리 가능!
로딩 상태 관리 : 컴포넌트의 로딩 상태를 관리. 비동기 작업이 진행되는 동안 로딩 상태를 표시하고, 작업이 완료되면 자동으로 해당 컴포넌트를 렌더링
지연된 로딩 : 지연된 로딩 구현 가능. 컴포넌트가 필요한 데이터나 리소스를 동적으로 로딩하고, 로딩이 완료되기 전까지 로딩 상태를 표시. 초기 로딩 시간을 최적화 + 필요한 부분만 렌더링
에러 처리: 비동기 작업 중 발생하는 에러 처리 기능을 제공. 비동기 작업이 실패하면 에러 처리를 위한 대체 컴포넌트나 에러 메시지를 보여줄 수 있음.
suspense for Data Fetching :
컴포넌트에서는 비동기적인 리소스를 선언 + 그 값을 읽어온다고 선언하기만 함.
그러면 실제로 로딩상태나 에러상태 처리는 컴포넌트를 감싸는 부모컴포넌트가 대신해줌
⇒ 이렇게 어떤 코드 조각을 감싸는 맥락으로 책임을 분리하는 방식을 대수적 효과(Algebraic Effects)라고 함.
그동안 비동기 처리는 React를 쓰는 개발자들에게 가장 까다로운 문제 중 하나였는데,,,
import React, { Suspense } from 'react';
// 비동기로 로딩되는 컴포넌트
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// 로딩 중에 표시할 컴포넌트
const LoadingSpinner = () => <div>Loading...</div>;
const App = () => {
return (
<div>
<h1>My App</h1>
<Suspense fallback={<LoadingSpinner />}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
React.lazy()
함수를 사용하여 LazyComponent
를 비동기로 로딩Suspense
컴포넌트로 LazyComponent
를 감싸서 로딩 상태를 관리fallback
prop은 로딩 중에 표시할 대체 컴포넌트 지정( LoadingSpinner
컴포넌트)LazyComponent
가 실제로 필요할 때까지 로딩을 지연시키고, 로딩이 완료되지 않은 동안 LoadingSpinner
를 보여줌. 이를 통해 초기 로딩 시간을 최적화하고 필요한 컴포넌트만 렌더링할 수 있습니다.import React, { Suspense } from 'react';
// 비동기로 로딩되는 컴포넌트
const LazyComponent = React.lazy(() => import('./LazyComponent'));
// 로딩 중에 표시할 Skeleton UI 컴포넌트
const SkeletonUI = () => (
<div style={{ background: '#f0f0f0', width: '200px', height: '100px' }}></div>
);
const App = () => {
return (
<div>
<h1>My App</h1>
<Suspense fallback={<SkeletonUI />}>
<LazyComponent />
</Suspense>
</div>
);
};
export default App;
✅(오늘 토의에서 내가 잘못알고있던 부분)
React.lazy와 Suspense 모두 코드 스플리팅을 쉽게 구현하고 애플리케이션의 성능을 개선하기 위해 사용되지만 각각 다른 목적과 사용 방법을 가지고 있음!!
const MyComponent = React.lazy(() => import('./MyComponent'));
✅ +) lazy Loading 과 SEO 의 관계성, 작동원리 (금일 토의 중 나눈 얘기)
https://ideadigitalagency.net/blog/lazy-loading-seo/
Suspense
와 React.lazy()
를 함께 사용하는 이유는 코드 분할과 비동기 컴포넌트 로딩을 효율적으로 관리하고, 사용자 경험을 개선하기 위함.
초기 번들 크기 감소: React.lazy()
는 컴포넌트를 동적으로 로딩하여 초기 번들 크기를 줄일 수 있음. 필요한 컴포넌트가 사용되기 전까지 해당 컴포넌트는 번들에 포함x, 별도의 작은 번들로 분리되어 로드됨.
⇒ 초기 로딩 속도를 향상시키고 애플리케이션의 성능을 개선!
지연된 컴포넌트 로딩: React.lazy()
로 동적으로 로딩된 컴포넌트를 Suspense
컴포넌트 내에서 사용하면, 로딩 중에 대체 컨텐츠나 로딩 스피너를 표시할 수 있습니다. 이는 사용자에게 로딩 상태를 시각적으로 전달하여 사용자 경험을 향상시키는 데 도움을 줍니다.
모듈 단위 코드 분할: React.lazy()
를 통해 컴포넌트를 모듈 단위로 분할 가능.
즉, 컴포넌트가 개별적으로 + 필요한 시점에 로딩 ⇒ 로딩 속도 향상 + 불필요한 자원낭비x
React.lazy를 단독으로 사용했을 때:
Suspense를 단독으로 사용했을 때:
함께 사용했을 때:
리액트의 렌더링 최적화를 위한 신규 업데이트는! 자동배칭(Automatic Batching) 🎉 🎉
ReactDOM.createRoot
메서드를 기반으로 렌더링 할 경우 state update 작업은 자동으로 Batching 처리 되는 것 . 이것을 자동배정이라고 함.
이전에는 자동배칭이 없었다고 생각하실 수 있는데, 그렇지 않음. (다만 단점이 존재했을뿐..)
- 단점이 뭔지 궁금하다면..?
- 브라우저의 이벤트가 실행되는 중에만 Batching 작업을 수행 → 이벤트가 종료된 후에 실행되는 경우는 작업 불가!
- 이벤트 핸들러 내부의 state update 작업에 대해서만 Batching 이 가능(Promise나 setTimeout, Native Event Handler 내부 작업 불가)
우선 Batching
에 대해 간략하게 설명하자면,
const handleClick = () => {
setState1(newValue1);
setState2(newValue2);
setState3(newValue3);
};
https://web.dev/reduce-javascript-payloads-with-code-splitting/
https://web.dev/i18n/ja/preload-critical-assets/
https://blog.mathpresso.com/conceptual-model-of-react-suspense-a7454273f82e