-
Notifications
You must be signed in to change notification settings - Fork 1
Directory Structure
๐ก FSD Architecture๋ฅผ ์ปค์คํ ํ์ฌ ์ฌ์ฉํฉ๋๋ค.
- features, entities 2๋จ๊ณ๋ฅผ ํตํฉํด features๋จ๊ณ๋ก ์ค์ ํ์์ต๋๋ค.
![](https://private-user-images.githubusercontent.com/44726494/331767852-0890b39c-eb37-412c-ad64-1af820f95300.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2MTU1NTQsIm5iZiI6MTczOTYxNTI1NCwicGF0aCI6Ii80NDcyNjQ5NC8zMzE3Njc4NTItMDg5MGIzOWMtZWIzNy00MTJjLWFkNjQtMWFmODIwZjk1MzAwLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDEwMjczNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBmYTZmNGVhMDdhMDU1MWE3MDVlZWFmNjA0NWM3MTFlYjVkMjE1MGUzMjYyZTM5NWEyZTgyNTBjYjNiMDYxZDEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.x3r93EY2Y7B3OyhDTpfl-dvr6WSjAx_WBixWfoH_nXc)
๐ฆsrc
โฃ ๐app/
โฃ ๐providers/
โฃ ๐routers/
โ ๐index.tsx
โฃ ๐pages/
โฃ ๐widgets/
โฃ ๐features/
โฃ ๐entities/
โ ๐shared/
โฃ ๐axios/
โฃ ๐styles/
โฃ ๐ui/
โ ๐util/
์ ํ๋ฆฌ์ผ์ด์ ๋ก์ง์ด ์ด๊ธฐํ๋๋ ๊ณณ์ ๋๋ค. ํ๋ก๋ฐ์ด๋, ๋ผ์ฐํฐ, ์ ์ญ ์คํ์ผ, ์ ์ญ ํ์ ์ ์ธ ๋ฑ์ด ์ฌ๊ธฐ์์ ์ ์๋ฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ ์ง์ ์ ์ญํ ์ ํฉ๋๋ค.
-
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/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,
});
}
ํ์์ ๋ ์ด์ด๋ค์ ์ด์ฉํด ํ์ด์ง์ ์ฌ์ฉ๋๋ ๋ ๋ฆฝ์ ์ธ UI ์ปดํฌ๋ํธ๋ฅผ ๋ง๋๋ ๋จ๊ณ์ ๋๋ค.
-
FeedMainHeader.tsx
-
FeedMain.tsx
...
๋น์ฆ๋์ค ๋ก์ง์ ํฌํจํ๋ View๋ค์ด ์ด ๋จ๊ณ์์ ์ ์๋ฉ๋๋ค.
-
hooks/useLike.tsx
-
Like.tsx
-
FeedItem.tsx
...
๋น์ฆ๋์ค ์ํฐํฐ๋ฅผ ์ ์ํฉ๋๋ค. ์ฌ์ฉ์, ๋ฆฌ๋ทฐ ๋ฑ์ด ํฌํจ๋ฉ๋๋ค.
- users/
- feeds/
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ์ ์ ํธ๋ฆฌํฐ ํจ์, 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๋ค์ด ์์นํ๋ ๋๋ ํฐ๋ฆฌ์ ๋๋ค.
![](https://private-user-images.githubusercontent.com/44726494/311426823-b0ed9c45-4273-4531-8619-69504bf39ac9.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2MTU1NTQsIm5iZiI6MTczOTYxNTI1NCwicGF0aCI6Ii80NDcyNjQ5NC8zMTE0MjY4MjMtYjBlZDljNDUtNDI3My00NTMxLTg2MTktNjk1MDRiZjM5YWM5LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDEwMjczNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE3MDM1MWYwZmIwYTk5MDkzYjBhNDI3NmJhMjY0NTIwYzNkNGJhNTA2MjI4MzQ3NzQwZGIzMzQ2Y2Y3MDdjZjImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.qfpcY_dFdkEepESuC7TdIB7969itKTYGO5DH0khZMI8)
![](https://private-user-images.githubusercontent.com/44726494/311426833-614afcc4-29c7-4464-9c9b-f9f076a3a94b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2MTU1NTQsIm5iZiI6MTczOTYxNTI1NCwicGF0aCI6Ii80NDcyNjQ5NC8zMTE0MjY4MzMtNjE0YWZjYzQtMjljNy00NDY0LTljOWItZjlmMDc2YTNhOTRiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDEwMjczNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTAzNDVjYmY3OWFlNjY2MGVkNzFkYWJjMjRmMWY3MWYzYzVhMjkyNzFmNWZjZmFiNTE3MjA5YjFmZTVmNDRkMzQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.calWk6k54QMaoxl-eG_UFDCAZDeLb_-dmSeSyW3T_zs)
![](https://private-user-images.githubusercontent.com/44726494/311426879-a708354f-d518-4f74-884b-d5b9a0e90e9b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3Mzk2MTU1NTQsIm5iZiI6MTczOTYxNTI1NCwicGF0aCI6Ii80NDcyNjQ5NC8zMTE0MjY4NzktYTcwODM1NGYtZDUxOC00Zjc0LTg4NGItZDViOWEwZTkwZTliLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTAyMTUlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwMjE1VDEwMjczNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFjMDU1ZGZjMTA5Y2Q2ZTc2NGMzYThhNjMyNzk3N2EwYzE5NTZjZmNmZTllZGUyZWU2ODk0NmJjNjljYzk1MDkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.X5LoRqKNTrthlpud8jLPa79RWSXFOYTGZ0tsou8Zv8U)
// 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];
};