현대 웹 애플리케이션은 사용자 경험(UX)을 향상시키기 위해 다양한 기술과 패턴을 활용합니다. 그 중 하나가 바로 무한 스크롤(Infinite Scroll)입니다. 무한 스크롤은 사용자가 페이지를 아래로 스크롤할 때 자동으로 새로운 콘텐츠를 로드하여 페이지 전환 없이도 끊김 없는 탐색을 가능하게 합니다. Facebook, Twitter, Instagram과 같은 소셜 미디어 플랫폼에서 흔히 볼 수 있는 기능입니다.
이 블로그에서는 React를 사용하여 무한 스크롤을 구현하는 방법에 대해 상세히 알아보겠습니다. 기본적인 개념부터 실제 코드 구현, 그리고 성능 최적화와 사용자 경험 개선을 위한 팁까지 모두 다룹니다.
무한 스크롤을 구현하는 방법은 크게 두 가지로 나뉩니다.
각 방법을 단게별로 살펴보겠습니다.
axios
를 설치합니다import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [posts, setPosts] = useState([]);
return (
<div className="App">
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</div>
);
}
export default App;
useEffect(() => {
fetchPosts();
}, []);
const fetchPosts = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts?_limit=10');
setPosts(res.data);
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const handleScroll = () => {
if (window.innerHeight + document.documentElement.scrollTop !== document.documentElement.offsetHeight) return;
fetchMorePosts();
};
const [page, setPage] = useState(1);
const fetchMorePosts = async () => {
setPage(prevPage => prevPage + 1);
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
setPosts(prevPosts => [...prevPosts, ...res.data]);
};
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const fetchMorePosts = async () => {
setLoading(true);
try {
setPage(prevPage => prevPage + 1);
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
setPosts(prevPosts => [...prevPosts, ...res.data]);
} catch (err) {
setError(true);
}
setLoading(false);
};
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(2);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
fetchPosts();
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const fetchPosts = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts?_page=1&_limit=10');
setPosts(res.data);
};
const fetchMorePosts = async () => {
setLoading(true);
try {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
setPosts(prevPosts => [...prevPosts, ...res.data]);
setPage(prevPage => prevPage + 1);
} catch (err) {
setError(true);
}
setLoading(false);
};
const handleScroll = () => {
if (window.innerHeight + document.documentElement.scrollTop + 1 >= document.documentElement.scrollHeight) {
fetchMorePosts();
}
};
return (
<div className="App">
{posts.map(post => (
<div key={post.id} style={{ margin: '20px', borderBottom: '1px solid #ccc' }}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
{loading && <h4>Loading...</h4>}
{error && <h4>Error occurred</h4>}
</div>
);
}
export default App;
react-infinite-scroll-component
라이브러리를 설치합니다.npm install react-infinite-scroll-component
InfiniteScroll
컴포넌트를 사용하여 코드를 간소화합니다.import InfiniteScroll from 'react-infinite-scroll-component';
function App() {
// 이전 코드와 동일한 상태 및 함수들
return (
<InfiniteScroll
dataLength={posts.length}
next={fetchMorePosts}
hasMore={true}
loader={<h4>Loading...</h4>}
>
{posts.map(post => (
<div key={post.id} style={{ margin: '20px', borderBottom: '1px solid #ccc' }}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</InfiniteScroll>
);
}
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import InfiniteScroll from 'react-infinite-scroll-component';
function App() {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(2);
useEffect(() => {
fetchPosts();
}, []);
const fetchPosts = async () => {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts?_page=1&_limit=10');
setPosts(res.data);
};
const fetchMorePosts = async () => {
const res = await axios.get(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);
setPosts(prevPosts => [...prevPosts, ...res.data]);
setPage(prevPage => prevPage + 1);
};
return (
<InfiniteScroll
dataLength={posts.length}
next={fetchMorePosts}
hasMore={page <= 10}
loader={<h4>Loading...</h4>}
endMessage={<p style={{ textAlign: 'center' }}><b>모든 데이터를 로드했습니다.</b></p>}
>
{posts.map(post => (
<div key={post.id} style={{ margin: '20px', borderBottom: '1px solid #ccc' }}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</div>
))}
</InfiniteScroll>
);
}
export default App;
스크롤 이벤트는 매우 빈번하게 발생하므로, 성능 저하를 방지하기 위해 쓰로틀링이나 디바운싱을 적용할 수 있습니다. Lodash의 throttle
함수를 사용하여 구현할 수 있습니다.
import _ from 'lodash';
useEffect(() => {
const throttledHandleScroll = _.throttle(handleScroll, 1000);
window.addEventListener('scroll', throttledHandleScroll);
return () => window.removeEventListener('scroll', throttledHandleScroll);
}, []);
많은 양의 데이터를 렌더링하면 DOM에 부담이 될 수 있습니다. react-window
나 react-virtualized
라이브러리를 사용하여 가상 스크롤을 구현하면 성능 향상을 기대할 수 있습니다.
단순한 텍스트보다 시각적인 로딩 스피너를 사용하여 사용자 경험을 향상시킬 수 있습니다.
데이터 로드에 실패했을 때 사용자에게 명확한 에러 메세지를 제공하고, 재시도 옵션을 제공합니다.
페이지가 길어질 경우 맨 위로 쉽게 이동할 수 있는 버튼을 제공하는 것이 좋습니다.
무한 스크롤은 현대 웹 애플리케이션에서 사용자 경험을 향상시키는 중요한 기능입니다. React에서 무한 스크롤을 구현하는 방법은 수동 구현과 라이브러리 사용으로 나뉘며, 각 방법에는 장단점이 있습니다.
프로젝트의 요구 사항과 팀의 역량에 따라 적절한 방법을 선택하시기 바랍니다. 또한 성능 최적화와 사용자 경험 개선을 위한 추가적인 고려 사항들을 염두에 두고 개발하면 더욱 완성도 높은 애플리케이션을 만들 수 있습니다.