Skip to content

Directory Structure

BangDori edited this page May 18, 2024 · 9 revisions

์ „์ฒด ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ตฌ์กฐ

๐Ÿ’ก FSD Architecture๋ฅผ ์ปค์Šคํ…€ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • features, entities 2๋‹จ๊ณ„๋ฅผ ํ†ตํ•ฉํ•ด features๋‹จ๊ณ„๋กœ ์„ค์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿ“ฆsrc
 โ”ฃ ๐Ÿ“‚app/
    โ”ฃ ๐Ÿ“‚providers/
    โ”ฃ ๐Ÿ“‚routers/
    โ”— ๐Ÿ“œindex.tsx
 โ”ฃ ๐Ÿ“‚pages/
 โ”ฃ ๐Ÿ“‚widgets/
 โ”ฃ ๐Ÿ“‚features/
 โ”ฃ ๐Ÿ“‚entities/
 โ”— ๐Ÿ“‚shared/
    โ”ฃ ๐Ÿ“‚axios/
    โ”ฃ ๐Ÿ“‚styles/
    โ”ฃ ๐Ÿ“‚ui/
    โ”— ๐Ÿ“‚util/
  1. ๐Ÿ“‚app
  2. ๐Ÿ“‚pages
  3. ๐Ÿ“‚widgets
  4. ๐Ÿ“‚features
  5. ๐Ÿ“‚entities
  6. ๐Ÿ“‚shared

๐Ÿ“‚app

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ์ง์ด ์ดˆ๊ธฐํ™”๋˜๋Š” ๊ณณ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ๋ฐ”์ด๋”, ๋ผ์šฐํ„ฐ, ์ „์—ญ ์Šคํƒ€์ผ, ์ „์—ญ ํƒ€์ž… ์„ ์–ธ ๋“ฑ์ด ์—ฌ๊ธฐ์—์„œ ์ •์˜๋ฉ๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ง„์ž…์  ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

  • providers: RouterProvider, QueryClientProvider ์™€ ๊ฐ™์€ ํ”„๋กœ๋ฐ”์ด๋”๋“ค์ด ์œ„์น˜ํ•˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
// app/providers/query-client.ts

import {
  MutationCache,
  QueryCache,
  QueryClient,
  QueryClientConfig,
} from "@tanstack/react-query";

export const queryClientOptions: QueryClientConfig = {
  defaultOptions: {},
  queryCache: new QueryCache({}),
  mutationCache: new MutationCache({}),
};

export const queryClient = new QueryClient(queryClientOptions);
  • routers: React Router์— ์˜ํ•ด ๋ Œ๋”๋ง๋˜๋Š” ํŽ˜์ด์ง€์˜ ๋ผ์šฐํ„ฐ๊ฐ€ ์ •์˜๋ฉ๋‹ˆ๋‹ค.
// app/routers/index.tsx

import { createBrowserRouter, RouteObject } from "react-router-dom";

import RootLayout from "@pages/RootLayout";
import ErrorLayout from "@pages/ErrorLayout";

const routes: RouteObject[] = [
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorLayout />,
  },
];

const router = createBrowserRouter(routes);
export default router;
  • index.tsx: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ง„์ž…์  ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‚pages

ํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์—ฌ๊ธฐ์—์„œ ์ •์˜๋ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ์ง„์ž…์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ปค์Šคํ…€ ํ›…๋„ ์—ฌ๊ธฐ์—์„œ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

// pages/feeds/FeedsPage.tsx

import { useGetFeeds } from "./useFeeds";

const FeedsPage = () => {
  const { feeds } = useFeeds();

  return <Feeds feeds={feeds} />;
};

export default FeedsPage;
// pages/feeds/useFeeds.tsx

async function getFeeds(): Promise<Feed[]> {
  const { data } = await axiosInstance.get("/feeds");

  return data;
}

export function useGetFeeds(): Feed[] {
  const fallback: Feed[] = [];

  const { data = fallback } = useQuery({
    queryKey: [queryKeys.feeds],
    queryFn: getFeeds,
  });

  return data;
}

export function usePrefetchFeeds(): void {
  const queryClient = useQueryClient();

  queryClient.prefetchQuery({
    queryKey: [queryKeys.feeds],
    queryFn: getFeeds,
  });
}

๐Ÿ“‚widgets

ํ•˜์œ„์˜ ๋ ˆ์ด์–ด๋“ค์„ ์ด์šฉํ•ด ํŽ˜์ด์ง€์— ์‚ฌ์šฉ๋˜๋Š” ๋…๋ฆฝ์ ์ธ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๋‹จ๊ณ„์ž…๋‹ˆ๋‹ค.

feeds/

  • FeedMainHeader.tsx

  • FeedMain.tsx

...

๐Ÿ“‚features

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํฌํ•จํ•˜๋Š” View๋“ค์ด ์ด ๋‹จ๊ณ„์—์„œ ์ •์˜๋ฉ๋‹ˆ๋‹ค.

feeds/

  • hooks/useLike.tsx

  • Like.tsx

  • FeedItem.tsx

...

๐Ÿ“‚entities

๋น„์ฆˆ๋‹ˆ์Šค ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž, ๋ฆฌ๋ทฐ ๋“ฑ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.

  • users/
  • feeds/

๐Ÿ“‚shared

์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ์™€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜, axios ์„ค์ • ๋“ฑ์ด ์—ฌ๊ธฐ์—์„œ ์ •์˜๋ฉ๋‹ˆ๋‹ค.

  • axios: axios ์„ค์ •์ด ํฌํ•จ๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
// shared/axios/config.ts

import axios from "axios";

const SERVER_URL = process.env.REACT_APP_SERVER_URL;

export const axiosInstance = axios.create({
  baseURL: '',
  timeout: 20000,
  headers: {
    "Content-Type": "application/json",
    accept: "application/json",
  },
});

axiosInstance.interceptors.request.use(onRequest);
axiosInstance.interceptors.response.use(onResponse, onError);
  • styles: scss์˜ ์ „์—ญ ๋ณ€์ˆ˜์™€ ์ดˆ๊ธฐ ์„ค์ •์ด ์œ„์น˜ํ•˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
// shared/styles/font.scss

$font-large: 3rem;
$font-medium: 1.6rem;
$font-default: 1.2rem;
$font-small: 0.8rem;

// shared/styles/color.scss

$color-white: #ffffff;
$color-black: #000000;

$color-gray-400: #bdbdbd;
$color-gray-500: #9e9e9e;
$color-gray-700: #616161;

$color-red-400: #ef5350;
$color-red-500: #f44336;
$color-red-700: #d32f2f;

$color-blue-400: #42a5f5;
$color-blue-500: #2196f3;
$color-blue-700: #1976d2;
  • ui: ์ž์ฃผ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋Š”, View๋“ค์ด ์œ„์น˜ํ•˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
// shared/ui/Button.tsx

const Button = ({ onClick, color, data }) => {
  return (
    <button className="button-style" onClick={onClick}>
      {data}
    </button>
  );
};
  • util: ํŠน์ • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ข…์†๋˜์ง€ ์•Š์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๊ฐ€ ํฌํ•จ๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ์ž…๋‹ˆ๋‹ค.
// shared/util/key-factories.ts

export const generateUserKey = (userId: number) => {
  return [queryKeys.user, userId];
};

export const generateUserAppointmentsKey = (
  userId: number,
  userToken: string
) => {
  return [queryKeys.appointments, queryKeys.user, userId, userToken];
};