리액트 성능 최적화 기법
리액트(React)는 사용자 인터페이스를 구축하기 위한 자바스크립트 라이브러리로, 컴포넌트 기반 구조를 통해 복잡한 애플리케이션을 효율적으로 관리할 수 있습니다. 하지만 애플리케이션의 규모가 커짐에 따라 성능 문제가 발생할 수 있습니다. 이 포스팅에서는 리액트 애플리케이션의 성능을 최적화하는 다양한 방법에 대해 살펴보겠습니다.
리액트 성능 최적화의 중요성
성능 최적화는 사용자 경험을 향상시키고, 애플리케이션의 반응성을 높이는 데 필수적입니다. 성능이 좋은 애플리케이션은 더 빠른 로드 시간과 더 부드러운 사용자 인터페이스를 제공하며, 이는 사용자 만족도와 유지율을 높이는 데 큰 역할을 합니다.
불필요한 렌더링 방지
리액트 컴포넌트는 상태나 props가 변경될 때마다 리렌더링됩니다. 따라서 불필요한 렌더링을 방지하는 것이 중요합니다.
React.memo 사용
React.memo
는 컴포넌트를 메모이제이션하여 props가 변경되지 않는 한 리렌더링을 방지합니다.
import React from 'react';
const MyComponent = React.memo(({ name }) => {
console.log('렌더링');
return <div>{name}</div>;
});
export default MyComponent;
위 예제에서 MyComponent
는 props가 변경되지 않는 한 리렌더링되지 않습니다.
shouldComponentUpdate 사용
클래스형 컴포넌트에서는 shouldComponentUpdate
메서드를 사용하여 렌더링 여부를 결정할 수 있습니다.
import React, { Component } from 'react';
class MyComponent extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.name !== this.props.name;
}
render() {
console.log('렌더링');
return <div>{this.props.name}</div>;
}
}
export default MyComponent;
위 예제에서 MyComponent
는 name
props가 변경될 때만 리렌더링됩니다.
컴포넌트 분할
큰 컴포넌트를 작은 컴포넌트로 분할하면 각 컴포넌트의 상태와 props 변경 시 리렌더링 범위를 최소화할 수 있습니다.
import React from 'react';
const Header = () => <header>헤더</header>;
const Footer = () => <footer>푸터</footer>;
const Page = () => (
<div>
<Header />
<main>메인 콘텐츠</main>
<Footer />
</div>
);
export default Page;
위 예제에서 Header
와 Footer
컴포넌트는 Page
컴포넌트와 분리되어 각각 독립적으로 리렌더링됩니다.
코드 분할
코드 분할은 애플리케이션의 초기 로딩 시간을 줄이고, 사용자가 필요로 할 때만 필요한 코드를 로드하는 방법입니다. 이를 통해 애플리케이션의 성능을 크게 향상시킬 수 있습니다.
React.lazy 사용
React.lazy
와 Suspense
를 사용하여 동적 import를 구현할 수 있습니다.
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const MyComponent = () => (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
export default MyComponent;
위 예제에서 OtherComponent
는 필요할 때만 로드되며, 로드 중에는 Loading...
메시지가 표시됩니다.
성능 최적화를 위한 훅 사용
리액트는 성능 최적화를 돕기 위해 몇 가지 훅을 제공합니다.
useCallback 사용
useCallback
훅은 함수형 컴포넌트에서 함수를 메모이제이션하여 불필요한 렌더링을 방지합니다.
import React, { useState, useCallback } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return <button onClick={handleClick}>Count: {count}</button>;
};
export default MyComponent;
useMemo 사용
useMemo
훅은 연산 비용이 높은 작업의 결과를 메모이제이션하여 성능을 최적화합니다.
import React, { useState, useMemo } from 'react';
const MyComponent = () => {
const [count, setCount] = useState(0);
const expensiveCalculation = (num) => {
console.log('비용이 많이 드는 계산');
return num * 2;
};
const result = useMemo(() => expensiveCalculation(count), [count]);
return (
<div>
<p>Result: {result}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default MyComponent;
가상화 리스트
리스트 렌더링 시, 많은 항목을 한꺼번에 렌더링하면 성능이 저하될 수 있습니다. 가상화 리스트를 사용하면 보이는 항목만 렌더링하여 성능을 최적화할 수 있습니다.
react-window 사용
react-window
는 리스트와 그리드 컴포넌트의 가상화를 위한 라이브러리입니다.
import React from 'react';
import { FixedSizeList as List } from 'react-window';
const MyComponent = () => {
const items = Array.from({ length: 1000 }, (_, index) => `Item ${index}`);
return (
<List
height={150}
itemCount={items.length}
itemSize={35}
width={300}
>
{({ index, style }) => <div style={style}>{items[index]}</div>}
</List>
);
};
export default MyComponent;
위 예제에서 react-window
는 보이는 항목만 렌더링하여 성능을 최적화합니다.
불변성 유지
리액트는 상태가 변경될 때마다 리렌더링을 트리거합니다. 상태를 불변으로 유지하면 변경이 발생한 경우에만 리렌더링을 수행할 수 있습니다.
불변성 유지 예제
import React, { useState } from 'react';
const MyComponent = () => {
const [items, setItems] = useState([1, 2, 3]);
const addItem = () => {
setItems([...items, items.length + 1]);
};
return (
<div>
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick={addItem}>Add Item</button>
</div>
);
};
export default MyComponent;
위 예제에서 setItems
함수는 기존 배열을 복사하여 새로운 항목을 추가합니다.
주요 포인트 요약 및 추가 학습 자료
이 포스팅에서는 리액트 애플리케이션의 성능을 최적화하는 다양한 방법에 대해 살펴보았습니다. 불필요한 렌더링을 방지하고, 컴포넌트를 분할하며, 코드 분할과 가상화 리스트를 사용하는 것이 주요 최적화 기법입니다. 추가로 학습할 자료는 다음 링크를 참고하시기 바랍니다: