FSD 구조 세팅

하니·2025년 1월 11일

React 길잡이

목록 보기
4/21

🗂️ providers

provider를 따로 관리하고자 providers 폴더를 만들어주었다.
📁 FSD-project-setting/src/app/providers

💠 QueryClientProvider

pnpm add @tanstack/react-query
pnpm add @tanstack/react-query-devtools

📁 FSD-project-setting/src/app/providers/QueryClientProvider.tsx

  • QueryClient 인스턴스 생성
  • throwOnError 옵션으로 에러를 ErrorBoundary로 전파
  • ReactQueryDevtools로 개발 환경에서 에러 테스트 가능
  • 일반 Error와 AxiosError 두 가지 에러 타입 테스트 가능
import {
  QueryClient,
  Query,
  QueryClientProvider as TanStackQueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { AxiosError } from "axios";

type Props = {
  children: React.ReactNode;
};

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      throwOnError: true,
    },
  },
});

export const QueryClientProvider = ({ children }: Props) => {
  return (
    <TanStackQueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools
        buttonPosition="bottom-left"
        errorTypes={[
          {
            name: "Error",
            initializer: errorInitializer(new Error("Error message")),
          },
          {
            name: "Axios Error",
            initializer: errorInitializer(new AxiosError("Axios error")),
          },
        ]}
      />
    </TanStackQueryClientProvider>
  );
};

function errorInitializer(error: Error) {
  return (query: Query) => {
    query.reset();
    return error;
  };
}

💠 react-error-boundary

pnpm add react-error-boundary

📁 FSD-project-setting/src/app/providers/ErrorBoundaryProvider.tsx

import { ErrorFallback } from "@/shared/ui/fallback/ErrorFallback";
import { ErrorInfo } from "react";
import { ErrorBoundary } from "react-error-boundary";

type Props = {
  children: React.ReactNode;
};

const handleError = (error: Error, info: ErrorInfo) => {
  console.log("에러 발생:", error);
  console.log("컴포넌트 스택:", info.componentStack);
};

export const ErrorBoundaryProvider = ({ children }: Props) => {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback} onError={handleError}>
      {children}
    </ErrorBoundary>
  );
};

📁 FSD-project-setting/src/shared/ui/fallback/ErrorFallback.tsx

import { FallbackProps } from "react-error-boundary";

export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
  return (
    <div>
      <h1>ErrorFallback</h1>
      <h2>문제가 발생했습니다</h2>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>재시도</button>
    </div>
  );
};

📁 RouterProvider.tsx

해당 프로젝트는 lazy와 suspense를 적용할 때 lazy로 발생한 suspense의 로딩 ui를 모두 같게 할 예정이다. 따라서 Suspense를 Router의 element에 각각 적용하기 번거롭기 때문에 Provider로 빼서 한번에 적용해주었다.
📁 FSD-project-setting/src/app/providers/RouterProvider.tsx

import React, { Suspense } from "react";

type Props = {
  children: React.ReactNode;
};

export const RouterProvider = ({ children }: Props) => (
  <Suspense fallback="Loading...">{children}</Suspense>
);

📁 index.tsx

나는 App.tsx에 여러 개를 적기보다는 Provider라는 태그로 한번에 적용하고 싶었다. 그래서 다음과 같이 Provider/index.tsx에 Provider를 한번에 모아서 적용하기로 했다.

import { ErrorBoundaryProvider } from "@/app/providers/ErrorBoundaryProvider";
import { QueryClientProvider } from "@/app/providers/QueryClientProvider";
import { RouterProvider } from "@/app/providers/RouterProvider";

type Props = {
  children: React.ReactNode;
};

export const Providers = ({ children }: Props) => (
  <QueryClientProvider>
    <ErrorBoundaryProvider>
      <RouterProvider>{children}</RouterProvider>
    </ErrorBoundaryProvider>
  </QueryClientProvider>
);

🗂️ routers

App.tsx에서 Providers가 모든 router들을 감싸주고 있다는 것을 명시적으로 알려주기 위해
RouterProvider를 Providers 폴더가 아니라 routers 폴더의 index.tsxroot(메인 라우터)에 RouterProvider를 따로 빼주었다.

📁 FSD-project-setting/src/app/routers/index.tsx

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

const root = createBrowserRouter([
  {
    path: "/",
    element: <div>Home Page</div>, // 기본 페이지 설정
  },
]);

const AppRouters = () => {
  return <RouterProvider router={root} />;
};

export default AppRouters;

App.tsx

결과

import { Providers } from "@/app/providers";
import AppRouters from "@/app/routers";

function App() {
  return (
    <Providers>
      <AppRouters />
    </Providers>
  );
}

export default App;

💠 Styled-Component

pnpm add styled-reset styled-components

app 폴더 안에 넣을지 shared/styles 폴더 안에 넣을지 고민했지만, GlobalStyle은 여러 레이어에서 재사용 된다기보다는 앱의 전역 설정으로 간주된다는 표현이 더 알맞다고 생각되어 app 레이어에 두었다.

📁 FSD-project-setting/src/app/styles/global.style.ts

import { createGlobalStyle } from "styled-components";
import reset from "styled-reset";

const GlobalStyle = createGlobalStyle`
	${reset}

	* {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
	
	:root {
		/* colors */

		/* fonts */
	}
`;

export default GlobalStyle;

📁 FSD-project-setting/src/main.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "@/app/App";
import GlobalStyle from "@/app/styles/global.style";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <GlobalStyle />
    <App />
  </StrictMode>
);

💠 기타 라이브러리 설치

pnpm add react-router-dom

참고

FSD 공식 문서-React Query
[TanStack Query] QueryFactory로 API 관리하기

profile
Hi, I am HANI Developer(╹◡╹). .....1hani me?

0개의 댓글