프로젝트/BookStore 사이트

1202 React-Query 사용, 리팩토링

thinktank911 2025. 12. 2. 13:16

중간회고

주요 학습 주제

  • 타입과 모델
  • 데이터 흐름
  • 컴포넌트 작성
  • css 스타일링
  • 커스텀훅

 

1. 타입과 모델

  • Record<ColorKey, string>
  • key in HeadingSize
  • 제너릭타입 Pick, Omit

 

2. 데이터 흐름


Books

BooksList > Books > useBooks > books.api.ts > http.ts

 

  • 낙관적 업데이트

 

3. 컴포넌트 작성

  • 조건부 렌더링 : isEmpty && (컴포넌트) / {isChecked ? <컴포넌트1>:<컴포넌트2>}
  • map 루프

4. css 스타일링

display:flex
flex-direction : row(기본), column
align-items: 상하 정렬

display:grid - 바둑판
grid-template-column: repeat(몇개, 간격)

 

5. 커스텀 훅

생산성

1. 스니펫 사용
2. 기능 단위 작업 흐름 파악

 

KPT 회고


import alias

  • 상대경로 ➡️ 절대경로 변경

CRACO 이용

  • 참고링크 : https://craco.js.org/
  • 설치 : npm i -D @craco/craco
  • craco-alias 설치 : npm i -D craco-alias ➡️ 사라짐
    ➡️  react-app-alias 대체 : npm install --save-dev react-app-alias
  • tsconfig.paths.json 생성
    • tsconfig.json과 같은 경로 
    • { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }
  • craco.config.js 생성
const cracoAlias = require("craco-alias");

module.exports = {
  plugins: [
        { 
            plugin: cracoAlias, 
            options: {
                source: "tsconfig",
                baseUrl: ".",
                tsConfigPath: "tsconfig.paths.json",
                debug: false
            }
        }
    ],
};

 

  • tsconfig.json에 파일 확장 및 포함
"extends": "./tsconfig.paths.json",
  "include": [
    "src",
    "craco.config.js"
  ]

 

  • package.json 변경
// 변경 후
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",

// 변경 후
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",

 



중복 코드 제거

App.tsx 라우트 layout 중복 제거

api.ts 요청핸들러 메소드화



스니펫 만들기

스니펫 확장 도구

  • Snippet Generator(fiore)


useAuth 훅 만들기

import { login, resetPassword, resetRequest, signup } from "@/api/auth.api";
import { SignupProps } from "@/pages/Signup";
import { useAuthStore } from "@/store/authStore"
import { useAlert } from "./useAlert";
import { data, useNavigate } from "react-router-dom";
import { LoginProps } from "@/pages/Login";
import { useState } from "react";

export const useAuth = () => {
    const { showAlert } = useAlert();
    const navigate = useNavigate();

    // 상태
    const { storeLogin, storeLogout, isloggedIn } = useAuthStore();

    // 메소드
    // 로그인
    const userLogin = (data: LoginProps) => {
        login(data).then((res) => {
            // 상태 변화
            storeLogin(res.token);

            // 성공
            showAlert('로그인이 완료되었습니다.');
            navigate('/');
        }).catch((err) => {
            // 실패
            showAlert('로그인에 실패했습니다.');
        });
    }

    // 회원가입
    const userSignup = (data: SignupProps) => {
        signup(data).then((res) => {
            // 성공
            // window.alert('회원가입이 완료되었습니다.');
            showAlert('회원가입이 완료되었습니다.');
            navigate('/login');
        }).catch((err) => {
            // 실패
            showAlert('회원가입에 실패했습니다.');
        });
    }

    // 비밀번호 초기화
    const userResetPassword = (data: SignupProps) => {
        resetPassword(data).then(() => {
            // 성공
            showAlert('비밀번호 초기화되었습니다.');
            navigate('/login');
        }).catch((err) => {
            // 실패
            showAlert('비밀번호 초기화에 실패했습니다.');
        });
    }

    // 리셋 요청 여부
    const [resetRequested, setResetRequested] = useState(false);

    // 비밀번호 초기화 요청
    const userResetRequest = (data: SignupProps) => {
        resetRequest(data).then(() => {
            // 성공
            setResetRequested(true);
        }).catch((err) => {
            // 실패
            showAlert('비밀번호 초기화 요청에 실패했습니다.');
        })
    }

    // 리턴
    return { userLogin, 
            userSignup, 
            userResetPassword, 
            userResetRequest, 
            resetRequested
        };
}


React-Query 사용

서버 스테이트 라이브러리

 

쿼리 클라이언트 작성

 

queryClient.ts

import { QueryClient } from "@tanstack/react-query";

export const queryClient = new QueryClient();

 

App.tsx

import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from './api/queryClient';

return (
    <QueryClientProvider client={queryClient}>
      <BookStoreThemeProvider>
        {/* <ThemeSwitcher /> */}
          <RouterProvider router={router} />
      </BookStoreThemeProvider>
    </QueryClientProvider>
  )

 

useBooks.ts

const { data: booksData, isLoading: isBooksLoading } = useQuery({
        queryKey: ["books", location.search],
        queryFn: () =>
            fetchBooks({
            category_id: category_id ? Number(category_id) : undefined,
            news: news ? true : undefined,
            currentPage: page ? Number(page) : 1,
            limit: LIMIT,
            }),
    });
    
    
  return { 
        books: booksData?.books, 
        pagination: booksData?.pagination, 
        isEmpty: booksData?.books.length === 0,
        isBooksLoading 
     }

 


로딩 인디케이터

  • 확인법
    ➡️ 네트워크 : no throttling => slow 4G