1. 개요 및 기술 스택
최근 프로젝트에서 DataGrid(표)의 특정 로우(행) 클릭 시, 해당 행의 정보를 기반으로 상세 모달을 띄우고 유튜브 영상을 재생하는 기능을 구현했습니다. 이 글에서는 React Hooks, Material UI (MUI), **Zustand (전역 상태 관리)**를 사용하여 이 과정을 어떻게 효율적이고 깔끔하게 처리했는지 공유합니다.
🛠️ 사용된 주요 기술
| 기술 스택 | 역할 | 최적화 포인트 |
| Zustand | 전역 모달/다이얼로그 상태 관리 | props drilling 방지, 컴포넌트 간 결합도 최소화 |
| MUI DataGrid | 선수 목록 테이블 | 로우 클릭 이벤트 (onRowClick) 활용 |
| react-youtube | 모달 내 유튜브 플레이어 렌더링 | 간편한 재생/정지 제어 |
| useCallback | 이벤트 핸들러 최적화 | 불필요한 리렌더링 방지 |
2. 준비 단계: 환경 설정 및 라이브러리 설치
선수 목록에서 가져온 youtubeVideoId를 모달에서 재생하기 위해 react-youtube 라이브러리를 설치합니다.
npm install react-youtube
# 또는
yarn add react-youtube
3. 기술적 최적화 1: Zustand를 이용한 모달 전역 관리
가장 중요한 최적화는 모달의 상태(열림 여부, 모달 타입, 전달할 데이터)를 전역 상태 관리 라이브러리인 Zustand로 분리한 것입니다.
이를 통해 PlayerTable 컴포넌트가 직접 모달을 import 하거나 상태를 관리할 필요 없이, 어디서든 openModal('MODAL_NAME', { data }) 함수만 호출하면 됩니다.
useModalStore 및 GlobalModal 구조 (핵심)
// GlobalModal.jsx (GlobalModal 컴포넌트의 일부)
import PlayerInfoModal from "./PlayerInfoModal.jsx";
import { useModalStore } from "../../store/useModalStore.js";
const GlobalModal = () => {
const { modalType, modalProps } = useModalStore();
switch (modalType) {
// ... (기존 모달 케이스)
case "PLAYER_INFO": // ⭐️ 신규 모달 타입 등록
return (
<PlayerInfoModal
{...modalProps} // { player: selectedPlayer } 데이터 전달
/>
);
default:
return null;
}
};
//
4. 구현 단계: 로우 클릭 이벤트 처리
선수 목록을 보여주는 탭 컴포넌트 (AllTab.jsx 등)에서 DataGrid의 로우 클릭 이벤트를 받아 모달을 띄웁니다.
💡 최적화 포인트: 불필요한 API 호출 제거
기존 사용자 관리 페이지와 달리, 선수 목록 조회 시 이미 상세 정보(youtubeVideoId 포함)가 테이블 데이터(rows)에 포함되어 있다면, 로우 클릭 시 추가적인 API 호출 (fetchPlayerById)을 생략합니다. 이는 로딩 시간을 줄이고 서버 부하를 최소화하는 중요한 최적화입니다.
// src/pages/home/player/AllTab.jsx (PlayerTab 컴포넌트의 일부)
const { showDialog } = useDialogStore();
const { openModal } = useModalStore();
// ✅ 1. useCallback을 사용하여 핸들러 최적화
const handleRowClick = useCallback((params) => {
// 2. DataGrid에서 받은 로우 데이터(params.row)를 바로 사용
const selectedPlayer = params.row;
const { playerName, playerNum } = selectedPlayer;
// 3. showDialog로 사용자 의도 확인
showDialog({
title: "선수 상세정보 및 응원가",
message: `${playerName} 선수(${playerNum}번)의 상세 정보 확인 및 응원가를 재생하시겠습니까?`,
confirmText: "확인",
onConfirm: () => {
// 4. 확인 후, API 호출 없이 로우 데이터를 모달에 전달
openModal("PLAYER_INFO", { player: selectedPlayer });
},
});
}, [showDialog, openModal]); // useDialogStore와 useModalStore는 의존성에 포함
// ... (render)
<PlayerTableTemplate handleRowClick={handleRowClick} />
5. 기술적 최적화 2: 유튜브 모달 구현 및 제어
PlayerInfoModal에서는 전달받은 선수 정보와 youtubeVideoId를 활용하여 UI를 구성하고, 사용자 경험을 개선하기 위한 로직을 추가했습니다.
💡 최적화 포인트: 모달 닫기 시 유튜브 영상 정지
모달을 닫을 때(닫기 버튼 클릭, ESC 키, 외부 클릭) 유튜브 영상이 백그라운드에서 계속 재생되는 것을 방지합니다.
// src/components/modal/PlayerInfoModal.jsx (일부)
import YouTube from 'react-youtube';
import { useRef } from 'react';
const PlayerInfoModal = ({ player }) => {
// ... (생략)
const playerRef = useRef(null); // 플레이어 인스턴스 저장
const onReady = (event) => {
playerRef.current = event.target;
};
const handleClose = () => {
// ⭐️ 모달 닫기 시 pauseVideo() 호출하여 영상 정지
if (playerRef.current && typeof playerRef.current.pauseVideo === 'function') {
playerRef.current.pauseVideo();
}
closeModal();
};
return (
<Dialog open={true} onClose={handleClose} /* ... */ >
{/* ... (UI 구성) */}
{youtubeVideoId && (
<YouTube
videoId={youtubeVideoId}
onReady={onReady} // 인스턴스 저장
opts={{ playerVars: { autoplay: 0 } }} // 자동 재생 방지
/>
)}
</Dialog>
);
};
6. 디자인 개선: MUI Dialog 세련되게 다듬기
기존 MUI의 딱딱한 Dialog 스타일에서 벗어나, **구분선 (Divider)**과 테마 색상을 활용하여 정보를 명확하게 분리하고 시각적 만족도를 높였습니다.
- 타이틀: 삼성 라이온즈의 테마 색상(블루 #1A3168 및 골드 #FFD700)을 활용하여 배경 및 아이콘에 적용했습니다.
- 정보 분리: 선수 사진/이름 섹션, 상세 정보 섹션, 응원가 섹션 사이에 Divider를 명확히 삽입했습니다.
- 이미지: 등번호를 기반으로 한 선수 이미지 URL을 사용하여 원형(50%) 프로필 사진을 배치했습니다.
7. 결론
Zustand와 DataGrid의 onRowClick 이벤트를 결합하고, useCallback 및 API 호출 생략 등의 기술적 최적화를 적용함으로써, 우리는 재사용 가능하고 성능 최적화된 동적 모달 시스템을 구축할 수 있었습니다. 특히, react-youtube를 통한 간편한 미디어 제어는 사용자 경험을 크게 향상시켰습니다.
'React & Typescript' 카테고리의 다른 글
| 🚀 성능 최적화: React 커스텀 훅 useDebounce를 활용한 지연 검색 구현 (1) | 2025.12.12 |
|---|---|
| 📝 Zustand를 활용한 React 관리 시스템의 통합 메뉴 및 UI 상태 관리 전략 (0) | 2025.12.09 |
| Axios와 Fetch의 PUT 요청 차이점과 사용법 (0) | 2025.03.18 |
| React에서 이벤트 핸들러에 익명 함수를 사용하는 이유 (0) | 2025.03.14 |
| 리액트 외부API 연동 실습 (0) | 2023.12.29 |