내장 컴포넌트
리액트는 JSX에서 사용할 수 있는 몇 가지 내장 컴포넌트를 제공합니다.
내장 컴포넌트 | 설명 |
<Fragment> <>...</> |
<Fragment>, 또는 <>...</> 로 표기하며, 여러 JSX 노드를 함께 그룹화할 수 있습니다. 여러 JSX 노드를 하나의 그룹으로 묶고 싶을 때 사용하는 컴포넌트 |
<Profiler> | 리액트 컴포넌트가 얼마나 빠르게 렌더링되는지를 측정하고, 성능을 분석할 수 있게 도와주는 컴포넌트 |
<StrictMode> | 개발 중에 잠재적인 문제를 더 빨리 찾아내고 해결할 수 있도록 돕는 컴포넌트 |
<Suspense> React.lazy() |
자식 컴포넌트를 로딩하는 동안 fallback을 표시할 수 있습니다. 자식 컴포넌트를 비동기적으로 로딩할 때, 로딩이 완료되기 전까지 보여줄 화면을 지정 |
내장 컴포넌트 사용방법 (import)
리액트에서 기본 태그나 내장 컴포넌트들을 사용하기 위해서는
import를 통해 해당 모듈을 가져와야 합니다.
이를 위한 두 가지 주요 방법이 있습니다.
- React를 전체로 가져오는 방법
- 개별 컴포넌트를 직접 가져오는 방법
📝 React를 전체로 가져오는 방법
리액트 라이브러리 전체를 React라는 이름으로 가져오는 방법입니다.
예전에는 JSX 문법을 사용하려면 항상 React를 가져와야 했습니다.
<Fragment>, <StrictMode> 같은 리액트 컴포넌트들을 사용하기 위해서는 React를 import해야 했습니다.
import React from 'react';
사용예시
import React from 'react';
function MyComponent() {
return (
<React.Fragment>
<h1>Hello</h1>
<p>Welcome to my website!</p>
</React.Fragment>
);
}
📝 개별 컴포넌트를 직접 가져오는 방법
리액트 내에서 필요한 컴포넌트만 선택적으로 가져오는 방법입니다.
최신 버전의 리액트에서는 이 방법을 사용해 불필요한 코드 로드를 줄일 수 있습니다.
import React, {StrictMode, Fragment, Suspense, Profiler, lazy} from "react";
사용예시
import { Fragment, StrictMode } from 'react';
function MyComponent() {
return (
<Fragment>
<h1>Hello</h1>
<p>Welcome to my website!</p>
</Fragment>
);
}
Fragment
🔎 <Fragmentg></Fragment> • <></>
컴포넌트가 렌더링될 때 실제 DOM에는 나타나지 않고, 여러 요소를 그룹화하는 역할을 수행
<Fragment>는 JSX에서 단축 구문으로 사용되며,
<React.Fragment>는 완전한 형태의 React 요소이다.
Virtual DOM에서 컴포넌트 변화를 감지해 낼 때 효율적으로 비교할 수 있도록
컴포넌트 내부는 하나의 DOM 트리 구조로 이루어져야 한다는 규칙이 있기 때문이다.
컴포넌트에 여러 요소가 있다면, 반드시 부모 요소 하나로 감싸야한다.
🧐 <Fragment key="키값">
리액트에서 리스트를 렌더링할 때, 각 항목을 구분하기 위해 key 속성이 필요합니다.
key는 리액트가 어떤 항목이 변경, 추가, 또는 제거되었는지
효율적으로 파악할 수 있도록 도와줍니다.
이를 통해 성능을 최적화하고, 불필요한 재 렌더링을 피할 수 있습니다.
사용예시
예를 들어 map 함수를 사용할 경우, 사용될 요소의 최상위 요소에 key 값을 부여해야 한다.
따라서 이렇게 key 설정이 필요한 경우에는 단축 문법이 아닌 React.Fragment 기본 문법을 사용하면 된다.
import React, {Fragment} from "react";
const items: string[] = ['Apple', 'Banana', 'Cherry'];
function ItemList() {
return <>
{items.map((item, index) => (
<Fragment key={index}>
<div>안녕</div>
<div>{item}</div>
</Fragment>
))}
</>
);
}
Profiler
🔎 <Profiler>
<Profiler>는 프로그래밍 방식으로 React 트리의 렌더링 성능을 측정할 수 있습니다.
Profiler 가 오버헤드를 만들기 때문에 프로덕션 빌드에서는 비활성화되어 있다는 점 입니다.
🧐 <Profiler> • onRender 콜백함수 • id 속성
측정 대상이 되는 컴포넌트를 Profiler로 감싸고,
onRender 콜백을 통해 원하는 값을 얻을 수 있습니다.
트리 내의 컴포넌트가 업데이트를 커밋할 때마다 React가 호출하는
onRender 콜백(함수)와 UI 컴포넌트를 식별하기 위한 문자열 id (문자열)
두 개의 props가 요구됩니다.
<App>
<Profiler id="Sidebar" onRender={onRender}>
<Sidebar />
</Profiler>
<PageContent />
</App>
📝 <Profiler>
렌더링 성능을 측정하기 위해서 컴포넌트 트리를 <Profiler>로 감싸줍니다.
이처럼 사용할 수 있으며 복수의 Profiler 컴포넌트를 사용하여 다른 부분들을 계산할 수 있습니다.
Profiler 컴포넌트는 가벼운 컴포넌트 이지만, 조금의 CPU와 메모리를 잡아먹기 때문에, 필요할 때만 사용해야 합니다.
매개변수 | 설명 |
id | 성능을 측정하는 UI 컴포넌트를 식별하기 위한 문자열입니다. |
onRender | 프로파일링 된 트리 내의 컴포넌트가 업데이트될 때마다 React가 호출하는 onRender 콜백입니다. 렌더링 된 내용과 소요된 시간에 대한 정보를 받고 있습니다. |
import React, { Profiler } from 'react';
render(
<App>
<Profiler id="Navigation" onRender={callback}>
<Navigation />
</Profiler>
</App>,
);
📝 onRender 콜백함수
React는 onRender 콜백을 렌더링 된 내용과 같이 호출합니다.
onRender로 콜백 함수를 전달해야 합니다. 콜백 함수는 다음과 같습니다.
매개인자 | 설명 |
id | 방금 커밋된 Profiler 컴포넌트의 id의 인자값 위에도 언급한 바와 같이 복수의 Profiler 컴포넌트를 사용할 수 있는데, 이 때 복수의 컴포넌트를 사용할 시 어떤 컴포넌트가 해당 콜백을 실행시켰는지에 대해 알 수 있습니다. |
phase | 해당 컴포넌트가 마운트된건지, 아니면 어떤 이벤트 혹은 상태 변화로 인하여 리렌더링 된건지를 식별합니다. (mount, update) |
actualDuration | 현재 업데이트에 해당하는 자손 컴포넌트들을 렌더하는데 걸린 시간, 하위 트리가 얼마나 메모이제이션을 잘 활용하고 있는지를 암시합니다. 이상적으로 다수의 컴포넌트들은 특정 prop이 변할 경우에만 리렌더링이 필요하기 때문에 이 값은 초기 렌더링 이후 상당 부분 감소해야 합니다. |
baseDuration | Profiler 트리 내 개별 컴포넌트들의 렌더링 비용의 최악 케이스를 계산해줍니다. |
startTime | react가 현재 업데이트에 대해 렌더링을 시작한 시간 |
commitTime | react가 현재 업데이트를 커밋한 시간 |
function onRenderCallback(
id, // 방금 커밋된 Profiler 트리의 "id"
phase, // "mount"(트리가 방금 마운트가 된 경우) 혹은 "update"(트리가 리렌더링된 경우)
actualDuration, // 실제 렌더링 걸리는 시간 (/ms)
baseDuration, // 예상 렌더링 걸리는 시간 (/ms)
startTime, // 렌더링 시작 시간 (/ms)
commitTime // 렌더링 끝 시간 (/ms)
) {
// ▶ 렌더링 시간 집계 혹은 로그...
}
사용예시
import React, {StrictMode, Fragment, Profiler} from "react";
function ProfilerDetect(
id: string, // 성능감지 값을 구별하기 위한 식별자 (Profiler id="StrictModeTest")
phase: "mount" | "update" | "nested-update", // 상황을 구별하는 값 (mount설치 | update변화 | nested-update 나를 제외한 업데이트)
actualDuration: number, // 실제 렌더링 걸리는 시간 (/ms)
baseDuration: number, // 예상 렌더링 걸리는 시간 (/ms)
startTime: number, // 렌더링 시작 시간 (/ms)
commitTime: number // 렌더링 끝 시간 (/ms)
){
console.log(`id : ${id}
phase : ${phase}
actualDuration : ${actualDuration}
baseDuration : ${baseDuration}
startTime : ${startTime}
commitTime : ${commitTime}
commitTime - startTime : ${commitTime - startTime}
`
);
}
function StrictModeTest(): JSX.Element {
let numbers: number[] = [1,2,3,4,5];
return <Fragment key="123">
<Profiler id="StrictModeTest" onRender={ProfilerDetect}>
{
numbers.map((value,index)=>
<Fragment key={index}>
<div>안녕</div>
<div key={index}>{value}</div>
</Fragment>
)
}
</Profiler>
</Fragment>
}
export default function (): JSX.Element {
return <>
<StrictMode>
<StrictModeTest/>
</StrictMode>
</>
}
StrictMode
🔎 <StrictMode>
StrictMode는 애플리케이션 내의 잠재적인 문제를 알아내기 위한 도구
경고 메시지를 출력하기 위해 컴포넌트를 두 번 렌더링합니다.
Fragment와 같이 UI를 렌더링하지 않으며, 자손들에 대한 부가적인 검사와 경고를 활성화
Strict 모드는 개발 모드에서만 활성화되기 때문에, 프로덕션 빌드에는 영향을 끼치지 않는다.
➡️ <StrictMode> 주요 검사 항목
- 안전하지 않은 생명주기를 사용하는 컴포넌트 발견
- 문자열 ref 사용 경고
- 권장되지 않는 findDOMNode 사용 경고
- 컴포넌트 오류 검사
- 예전 방식 Context API 검사
- 컴포넌트 재사용 가능한 상태 보장
🧐 엄격모드 (StrictMode)
위 코드에서 Header과 Footer은 Strict모드 검사가 이루어지지않고
ComponentOne, ComponentTwo만 받고, 그 둘의 자손까지 모두 검사가 이루어진다.
import React from 'react';
function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}
Suspense • lazy()
🔎 <Suspense> • React.lazy()
선언적 Loading UI와 Code Splitting(코드 분할)을 위한
Suspense 와 React.lazy 부터 시작된다.
🧐 코드 분할 (Code Splitting)
코드 분할은 React 프로젝트를 빌드하면서 하나의 파일로 병합하는 번들링에서 요구되는 주요 컨셉이다.
그러나 이제는 번들링하게 되면 특정 지점에서 코드를 해석하고 실행하는 정도가 느려지게 되었다.
모던 웹으로 발전하면서 점점 DOM을 다루는 정도가 정교해지며
JavaScript 코드 자체가 방대해지고 무거워졌기 때문이다.
이것이 코드 분할의 핵심 아이디어이다.
번들이 거대해지는 것을 방지하기 위한 좋은 해결 방법은 번들을 물리적으로 나누는 것이다.
코드 분할은 런타임 시 여러 번들을 동적으로 만들고 불러오는 것으로,
Webpack, Rollup과 같은 번들러가 지원하는 기능이다.
따라서 코드 분할을 하게 되면 지금 당장 필요한 코드가 아니라면 따로 분리를 시키고,
나중에 필요할 때 불러와서 사용할 수 있다.
이를 통하여 대규모 프로젝트의 앱인 경우에도 페이지의 로딩 속도를 개선할 수 있게 된다.
그렇다면 어느 페이지에서 코드를 해석하고 실행하는 정도가 느려졌는지 파악해서
번들을 나눈 뒤에 지금 필요한 코드만 불러오고
나중에 필요한 코드는 나중에 불러올 수 있지 않을까?
🧐 Static Import (정적) vs Dynamic Import (동적)
React는 SPA(Single-Page-Application)인데,
사용하지 않는 모든 컴포넌트까지 한 번에 불러오기 때문에 첫 화면이 렌더링 될 때까지의 시간이 오래 걸린다.
그래서 사용하지 않는 컴포넌트는 나중에 불러오기 위해 코드 분할 개념을 도입했다.
React에서 코드 분할하는 방법은 Dynamic Import(동적 불러오기)를 사용하는 것이다.
➡️ Static Import (정적)
import는 JavaScript ES6에서 도입된 정적(Static) 모듈 가져오기 방식이다.
코드 작성 시, import ~ from ~; 모듈을 상단에 명시적으로 선언하고, 모듈을 로드한다.
Import는 아래 3가지의 특징을 가진다.
특징 | 설명 |
정적 로드 | 컴파일 시점에 모든 의존성이 결정되고 로드되어, 빌드 도구가 전체 모듈 그래프를 미리 알 수 있게 해준다. |
호이스팅(Hoisting) | import 문은 파일의 최상단으로 끌어올려진다. |
트리 쉐이킹(Tree Shaking) | 불필요한 코드(사용되지 않는 모듈)를 제거하여 최종 번들 크기를 줄일 수 있다. |
import React, { useState } from 'react';
import MyComponent from './MyComponent';
const App = () => {
return (
<div>
<MyComponent />
</div>
);
};
export default App;
➡️ Dynamic Import (동적)
Dynamic Import는 ES2020에서 도입된 동적(Dynamic) 모듈 가져오기 방식이다.
이는 함수처럼 호출하여 필요할 때 모듈을 가져오는 방식이다.
import('./파일명')은 자바스크립트의 동적 import() 함수로, 모듈을 비동기적으로 불러오는 데 사용됩니다.
그러나, 리액트는 이 방식만으로는 컴포넌트를 제대로 처리할 수 없습니다.
리액트의 동적 컴포넌트 로딩을 지원하기 위해서는 React.lazy()가 필요합니다.
import('./sum').then(sum => {
console.log(sum(1 + 2));
});
특징 | 설명 |
동적 로드 | 모듈이 필요할 때 런타임에 로드되어, 특정 상황(ex: 사용자 인터랙션)에 따라 모듈을 로드할 수 있다. |
비동기 처리 | import()는 프로미스를 반환하므로 async/await와 함께 사용하거나 .then을 사용하여 처리할 수 있다. |
코드 스플리팅 (Code Splitting) |
초기 로딩 시간을 줄이기 위해 필요한 모듈을 나중에 로드하는 방식으로, 애플리케이션의 성능을 향상시킬 수 있다. |
📌 lazy()
🧐 사용이유
컴포넌트를 동적으로 불러오기위해선 React.lazy를 사용해야한다.
따라서, 동적으로 컴포넌트를 불러오려면 React.lazy()와 import()를 함께 사용해야 하고,
Suspense를 통해 로딩 중 상태를 처리하는 것이 올바른 접근 방식입니다.
➡️ 동적으로 가져올 컴포넌트는 별도의 파일에 정의되어 있어야 합니다.
➡️ React.lazy()는 동적으로 가져올 컴포넌트를 import() 구문을 사용해 나중에 필요할 때 불러옵니다.
- React.lazy 함수를 이용하면 Dynamic import 사용해 컴포넌트를 렌더링 할 수 있음
- Dynamic import 통해 초기 렌더링 지연 시간을 감소할 수 있음
- React.lazy로 감싼 컴포넌트는 단독으로 쓰일 수 없고,
React.suspense 컴포넌트의 하위에서 렌더링 해야 함
📝 React.lazy() 문법
동적 import를 호출하는 함수를 인자로 받아 컴포넌트를 Promise 비동기로 반환
import Component from './Component';
/* React.lazy로 dynamic import를 감쌉니다. */
const LazyComponent = React.lazy(() => import('./Component'));
사용예시
import { Suspense } from 'react';
const SomeComponent = React.lazy(() => import('./SomeComponent'));
const MyComponent = () => {
return (
<Suspense fallback={<div>로딩 중. . .</div>}>
<SomeComponent />
</Suspense>
);
}
📌 Suspense
- Router로 분기가 나누어진 컴포넌트들을 lazy를 통해 import 하면
➡️ path로 이동할 때 컴포넌트를 불러오게 되는데 이 과정에서 로딩시간 발생 - Suspense는 아직 로딩이 준비되지 않은 컴포넌트가 있을 때
로딩 화면을 보여주고, 준비가 완료되면 컴포넌트를 보여주는 기능 - fallback 속성은 컴포넌트가 로드될 때까지 기다리는 동안
로딩 화면으로 보여줄 리액트 엘리먼트를 받음 - Suspense 컴포넌트 하나로 여러 개의 lazy 컴포넌트를 보여줄 수 있음
📝 Suspense 문법
로딩 처리할 컴포넌트를 <Suspense> 컴포넌트로 감싸고,
로딩 중에 보여줄 fallback 요소를 인자로 전달하면 된다.
function SuspenseExample() {
return (
<Suspense fallback={<FallbackComponent />}>
<ContesntsComponent />
</Suspense>
)
}
사용예시
/* suspense 기능을 사용하기 위해서는 import 해와야 합니다. */
import { Suspense, lazy } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
{/* 이런 식으로 React.lazy로 감싼 컴포넌트를 Suspense 컴포넌트의 하위에 렌더링합니다. */}
<Suspense fallback={<div>Loading...</div>}>
{/* Suspense 컴포넌트 하위에 여러 개의 lazy 컴포넌트를 렌더링시킬 수 있습니다. */}
<OtherComponent />
<AnotherComponent />
</Suspense>
</div>
);
}
✅ lazy()와 Suspense의 적용
- 앱에 코드 분할을 도입할 곳을 결정하는 것은 사실 까다로움
➡️ 웹 페이지를 불러오고 진입하는 단계인 Route에 이 두 기능을 적용시키는 것이 좋음 - Route에 기능을 적용 시키면 초기 렌더링 시간이 줄어드는 분명한 장점은 있으나
페이지를 이동하는 과정마다 로딩 화면이 보여지기 때문에
서비스에 따라 적용 여부를 결정해야 함
import { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
사용예시
main.tsx 파일이 실행될 때, MyComponent는 처음부터 불러와지지 않고,
실제로 <LazyComponent />가 렌더링될 때 MyComponent.tsx 파일이 동적으로 로드됩니다.
이렇게 하면 초기 로딩 시간이 줄어들고, 필요할 때만 해당 컴포넌트를 불러와서 사용할 수 있습니다.
이 방식으로 React.lazy()를 통해 동적 컴포넌트 로딩을 구현할 수 있습니다.
사용자는 컴포넌트가 로드되는 동안 대기 상태를 인지할 수 있게 되고, 앱의 사용자 경험이 향상됩니다.
➡️ LazyComponent 로드 전
<Suspense> 컴포넌트의 fallback 속성에 지정된 내용이 먼저 화면에 표시됩니다.
이 예시에서는 <div>Loading...</div>가 보여지게 됩니다. 즉, 'Loading...'라는 텍스트가 화면에 표시됩니다.
➡️ LazyComponent 로드 후
React.lazy()로 불러온 LazyComponent가 실제로 로드되고 나면, LazyComponent가 화면에 렌더링됩니다.
이 때 'Loading...' 텍스트는 사라지고, 실제 컴포넌트가 표시됩니다.
📜 MyComponent.tsx (동적으로 불러올 컴포넌트)
MyComponent라는 리액트 컴포넌트를 정의하고, 이를 export default로 내보냅니다.
이 컴포넌트는 나중에 React.lazy()를 통해 동적으로 불러올 수 있습니다.
import React from 'react';
// MyComponent 컴포넌트 정의
function MyComponent() {
return <div>Hello, I'm a lazy-loaded component!</div>;
}
export default MyComponent;
📜 main.tsx (메인 컴포넌트에서 React.lazy() 사용)
React.lazy()를 사용해 MyComponent.tsx 파일에서 MyComponent를 동적으로 불러옵니다.
이 때 React.lazy(() => import('./MyComponent')); 로 해당 컴포넌트를 가져옵니다.
Suspense 컴포넌트를 사용해, LazyComponent가 로드될 때까지 보여줄 대체 UI (fallback)를 지정합니다.
여기서는 "Loading..."이라는 텍스트를 보여줍니다.
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom';
// React.lazy()를 사용해 MyComponent를 동적으로 불러옵니다.
const LazyComponent = React.lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<h1>Main App Component</h1>
{/* Suspense로 감싸서 fallback UI를 지정합니다. */}
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
'📌 Front End > └ React' 카테고리의 다른 글
[React] 리액트 훅 - useReducer(관리 코드 컴포넌트 외부 분리) (0) | 2024.08.28 |
---|---|
[React] 리액트 이벤트 처리 - 핸들러, 컴포넌트 상태(useState), 단방향 바인딩 (0) | 2024.08.26 |
[React] 리액트 사용자 정의 함수형 컴포넌트(Functional Component) - 클래스와 객체 인스턴스를 사용해 데이터 관리 (0) | 2024.08.18 |
[React] React Vite TypeScript 개발환경 세팅하기 (Vite 설정 파일 - root: "./" 변경) (0) | 2024.08.17 |
[React] 리액트 컴포넌트(Component)와 Props 속성 (0) | 2024.08.17 |