[React 강의] Slack 클론 코딩[실시간 채팅 with React] 섹션3

강경서·2025년 9월 8일
post-thumbnail

🤝 섹션 3. 메뉴와 모달 만들기

워크스페이스

gravatar

gravatar 는 이메일 기반 글로벌 프로필 이미지 서비스이지만 간단한 해시 기반 아바타 생성기로도 사용이 가능합니다.

npm install gravatar
gravatar.url(email, options);

email 을 넣지 않더라도 아바타 생성이 가능합니다. option 에는 size, default, ratio 등을 구성하여 아바타 생성이 가능합니다.


중첩 라우팅

중첩 라우팅은 부모 라우트 안에 자식 라우트를 정의해서 레이아웃을 공유하거나 페이지 일부만 바꿔치기할 수 있게 해주는 기능입니다.

// front/layouts/App

<Routes>
	{/* 부모 라우트 */}
	<Route path="/workspace" element={<Workspace />}>
    	{/* 자식 라우트 */}
    	<Route path="channel" element={<Channel />} />
    	<Route path="dm" element={<DirectMessage />} />
  	</Route>
</Routes>

// front/layouts/Workspace 

const Workspace = () => {
	return (
    	<div>
        	<Outlet /> // 자식 라우터 랜더링
        </div>
    )
}

react-routerRoute 내부에 또 다른 Route 들을 구성하여 중첩 라우팅을 구성합니다. Outlet 을 사용하여 자식 라우터를 랜더링합니다.


메뉴 만들기

React 에서 컴포넌트단위로 나눠 개발하면 다양한 이점이 있습니다.( 재사용성, 유지보수성등 )

// front/components/Menu

import React, { CSSProperties, useCallback } from 'react';
import { CloseModalButton, CreateMenu } from './styles';

interface Props {
  children: React.ReactNode;
  style: CSSProperties;
  show: boolean;
  onCloseModal: () => void;
  closeButton?: boolean;
}

const Menu = ({ children, style, show, onCloseModal, closeButton = true }: Props) => {
  const stopPropagation = useCallback((event: any) => {
    event.stopPropagation(); // 이벤트 버블링 제어
  }, []);

  if (!show) {
    return null;
  }

  return (
    <CreateMenu onClick={onCloseModal}>
      <div style={style} onClick={stopPropagation}>
        {closeButton && <CloseModalButton onClick={onCloseModal}>&times;</CloseModalButton>}
        {children}
      </div>
    </CreateMenu>
  );
};

export default Menu;

// front/layouts/Workspace 

<Menu style={{ right: 0, top: 38 }} show={showUserMenu} onCloseModal={onClickUserProfile}>
	<ProfileModal>
		<img src={gravatar.url(useData.nickname, { s: '28px', d: 'retro' })} alt={useData.nickname} />
        <div>
			<span id="profile-name">{useData.nickname}</span>
			<span id="profile-active">Active</span>
		</div>
	</ProfileModal>
	<LogOutButton onClick={onLogout}>로그아웃</LogOutButton>
</Menu>

한 컴포넌트는 한 가지 역할만 수행하는 단일 책임 원칙을 준수하는 편이 좋습니다.

event.stopPropagation() 는 정 요소에서 발생한 이벤트가 상위 DOM 요소로 계속해서 전파되는 버블링 현상을 막습니다


모달 만들기

react-toastify

react-toastifyReact 에서 토스트 알림을 손쉽게 구현할 수 있게 해주는 라이브러리입니다.

npm install react-toastify
// front/latouts/App

const App = () => {
  return (
    <>
      <ToastContainer />
    </>
  );
};

최상단에 한 번만 ToastContainer 넣어두면 앱 어디서든 toast 호출 가능합니다.

// 토스트 호출
toast.success("성공적으로 저장되었습니다!");
toast.error("에러가 발생했습니다.");

// 토스트 위치 변경
<ToastContainer position="bottom-left" />
toast.error(message, { position: 'bottom-center' });

// 비동기 
toast.promise(
  fetch("/api/save"),
  {
    pending: "저장 중...",
    success: "저장 완료! 🎉",
    error: "저장 실패 😢"
  }
);

동적 라우팅

동적 라우팅은 URL의 일부를 변수처럼 받아서, 같은 컴포넌트로 여러 경로를 처리할 때 쓰입니다.

import { BrowserRouter, Routes, Route, useParams } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* 동적 라우트 */}
        <Route path="/users/:userId" element={<UserProfile />} />
      </Routes>
    </BrowserRouter>
  );
}

function UserProfile() {
  // useParams로 URL 파라미터 가져오기
  const { userId } = useParams();
  
  return <h1>사용자 ID: {userId}</h1>;
}

useParams 를 사용하여 컴포넌트 내에서 URL 파라미터를 가져올 수 있습니다.

// /search?keyword=react&page=2

import { useSearchParams } from 'react-router';

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();

  const keyword = searchParams.get('keyword'); // "react"
  const page = searchParams.get('page');       // "2"

  const handleNextPage = () => {
    searchParams.set('page', (Number(page) + 1).toString());
    setSearchParams(searchParams); // URL 변경
  };

  return (
    <div>
      <h1>검색 결과</h1>
      <p>검색어: {keyword}</p>
      <p>페이지: {page}</p>
      <button onClick={handleNextPage}>다음 페이지</button>
    </div>
  );
}

쿼리 파라미터는 useSearchParams 를 사용하여 편하게 읽고 쓸 수 있습니다. 기존에 사용되었던 useLocation + URLSearchParams 와 달리 useNavigate 를 사용하여 URL을 변경할 필요가 없습니다.


📝 후기

react-router의 버전이 달라짐에 따라 기존에 사용하던 방식이 많이 바뀌었습니다. 이렇게 라이브러리들의 사용법이 달라질 수 있으니 개발 및 이슈 발생 시에는 다른 개발자들의 포스팅 보다는 공식문서를 제일 먼저 확인해야할 것 같습니다.

실제 운영중인 서비스에서 라이브러리 버전 변경에 따른 큰 마이그레이션 작업이 발생할 때 효율적인 방법이 있을까?


🧾 Reference

profile
기록하고 배우고 시도하고

0개의 댓글