실시간으로 진행중인 게임방 목록을 메인화면에서 보여주기 위해 소켓으로 페이지네이션을 구현해보았다.
원래 UI는 무한스크롤(커서 기반 페이지네이션)이었지만 실시간으로 게임방 목록이 업데이트 될 경우 이전에 불러온 데이터에서 변화가 생길 수도 있고(입장인원 수 등), 목록이 최신순으로 업데이트 되면서 그 순서가 바뀔 수도 있기 때문에 충분히 데이터가 꼬일 수 있겠다는 생각이 들었다.
그래서 페이지네이션을 페이지 기반으로 변경하기로 하고 UI에는 페이지네이터 컴포넌트를 추가해주기로 했다.
그럼 stomp를 이용해 어떻게 구현할 것인가...
사실 page정보가 반영된 topic을 구독하고 서버가 던져준 데이터를 그대로 렌더링 시키면 된다. 그런데 메시지 자체는 데이터가 업데이트 되었을때만 보내준다는 것이 문제.. 첫 렌더링시에 topic을 구독만 한다고 메시지가 오는 것이 아니라 서버에 내가 구독을 시작했다는 것을 알려줘야 서버에서 첫 메시지를 받아올 수 있을 것이다. (아니면 첫 요청은 get요청으로 받아오고 이후에 소켓으로 업데이트 되는 것을 반영하는 방법도 있음)
구현 방법
1. `/sub/rooms?page=${pageNumber}` subscribe하기
2. `/pub/rooms?page=${pageNumber}` 로 publish 하기
3. subscription으로 온 메시지 데이터 렌더링하기
4. 페이지 변경시 이전 구독 취소하고 새 구독 시작하기
(반복)
우선 페이지 네이터 컴포넌트
import * as styles from './Paginator.css';
const Paginator = ({
currentPage,
totalPages,
handlePagePrev,
handlePageNext,
}: {
currentPage: number;
totalPages: number;
handlePagePrev: () => void;
handlePageNext: () => void;
}) => {
return (
<div className={styles.paginator}>
<div className={styles.previousButton}>
{currentPage > 1 ? (
<div
className={styles.triangularButton}
onClick={handlePagePrev}
></div>
) : (
<div className={styles.emptySpace}></div>
)}
</div>
<div className={styles.pageStatus}>
{currentPage} / {totalPages}
</div>
<div className={styles.nextButton}>
{currentPage < totalPages ? (
<div
className={styles.triangularButton}
onClick={handlePageNext}
></div>
) : (
<div className={styles.emptySpace}></div>
)}
</div>
</div>
);
};
export default Paginator;
간단하게 구현해 주었다
const [pageNum, setPageNum] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [pageData, setPageData] = useState<RoomInfo[]>([]);
const [subscription, setSubscription] = useState<StompSubscription | null>(null);
useEffect(() => {
if (subscription) {
subscription.unsubscribe();
}
subscribePage(pageNum);
getPage(pageNum);
}, [pageNum]);
useEffect(() => {
stompClient.onConnect = () => {
console.log('Connected to WebSocket');
subscribePage(pageNum);
getPage(pageNum);
};
stompClient.activate();
return () => {
stompClient.deactivate();
};
}, []);
메인 로직은 이런 식.
첫 렌더링 시 실행될 useEffect와 페이지가 바뀌면 실행될 useEffect로 구분된다
첫 렌더링 시에는 WebSocket 연결을 활성화하고, 연결이 완료되면 subscribePage(pageNum)과 getPage(pageNum)을 호출하여 초기 페이지 번호(1)에 대한 구독을 실행하고 데이터를 가져온다.
페이지가 바뀌면 기존 구독을 취소한 후, 새로운 페이지 번호에 대한 구독을 시작하고, 해당 페이지 데이터를 가져온다.
subscribePage와 getPage 코드
// import { StompSubscription } from '@stomp/stompjs'; 필요
const subscribePage = (pageNumber: number) => {
if (!stompClient.connected) return;
const sub = stompClient.subscribe(
`/sub/rooms?page=${pageNumber}`,
(message) => {
const pageData = JSON.parse(message.body);
setTotalPages(pageData.totalPages);
setPageData(pageData.rooms);
}
);
setSubscription(sub);
};
const getPage = (pageNumber: number) => {
if (!stompClient.connected) return;
stompClient.publish({
destination: `/pub/rooms?page=${pageNumber}`,
});
};
이전 구독을 취소하려면 subcribe할 때 반환하는 StompSubscription 객체를 받아와 subscription 상태로 저장해놓고 unsubscribe()메소드를 사용하면 된다.
Paginator에 props는 이런식으로 전달했다.
const handlePagePrev = () => {
if (pageNum > 1) {
setPageNum(pageNum - 1);
}
};
const handlePageNext = () => {
setPageNum(pageNum + 1);
};
// ...
return (
{/* ... */}
<Paginator
currentPage={pageNum}
totalPages={totalPages}
handlePagePrev={handlePagePrev}
handlePageNext={handlePageNext}
/>
);
완성된 화면!
'Front-End' 카테고리의 다른 글
[Next.js] Parallel Routes 모달에 적용하기 (0) | 2024.05.03 |
---|---|
[React/Next.js] 모달 컴포넌트 만들기 (0) | 2024.04.19 |
[React Native] react-native-keychain 사용하기 (0) | 2024.04.14 |
[Next.js] Next.js 14에서 페이지 이동 감지하기 (0) | 2024.04.12 |
[React / Next.js + Zustand] StompJS로 채팅 구현하기 (2) - 실시간 채팅 방 상태 관리하기 (2) | 2024.04.05 |