Logo

Changchangwoo's blog

TIL
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Today I Learned를 기록하는 공간입니다. 🔥🔥🔥
잔디를 클릭하면 내용확인이 가능합니다.

리액트 쿼리 팩토리 디자인 관리

MVP 1 ⇒ 구현된 API 전부 연결

MVP 2 ⇒ 사용자 경험을 위한 UI 반영 ( 무한 스크롤, 애니메이션 )

MVP 3 ⇒ 최적화

export const queryKeys = {
  library: {
    all: ['library'] as const,
    lists: () => [...queryKeys.library.all, 'list'] as const,
    list: (params: { lat: number; lng: number; libraryName?: string; page?: number; size?: number }) =>
      [...queryKeys.library.lists(), params] as const,
    details: () => [...queryKeys.library.all, 'detail'] as const,
    detail: (id: string) => [...queryKeys.library.details(), id] as const,
  },

  libraryBook: {
    all: ['library-book'] as const,
    lists: () => [...queryKeys.libraryBook.all, 'list'] as const,
    list: (params: {
      latitude: number;
      longitude: number;
      page: number;
      size: number;
      bookName?: string;
      sortType?: string;
    }) => [...queryKeys.libraryBook.lists(), params] as const,
  },
} as const;

export const mutationKeys = {
  library: {
    create: ['library', 'create'] as const,
    update: ['library', 'update'] as const,
    delete: ['library', 'delete'] as const,
  },
} as const;

  • 다음처럼 관리하면 쿼리키 관리에 용이
  1. list(params) 사용 - 자동 Refetch

  const { data } = useQuery(
    libraryQueries.GET_LIBRARY({ lat: 37.5, lng: 127.0 })
  );
  // queryKey: ['library', 'list', { lat: 37.5, lng: 127.0 }]

  params 값이 변경되면 React Query가 다른 쿼리로 인식해서 자동으로 새 데이터를 fetch합니다.

  // lat이 37.5 → 37.6으로 변경
  // queryKey: ['library', 'list', { lat: 37.6, lng: 127.0 }]
  // 👉 자동으로 새로운 API 호출!

  이건 "초기화"가 아니라 쿼리키가 달라져서 새로운 쿼리를 실행하는 겁니다.

  2. lists() 사용 - 수동 Invalidation

  queryClient.invalidateQueries({
    queryKey: queryKeys.library.lists()
  });
  // ['library', 'list']로 시작하는 모든 쿼리 무효화

  이건 prefix matching으로 동작합니다:

  ['library', 'list', { lat: 37.5, lng: 127.0 }]  ✅ 무효화됨
  ['library', 'list', { lat: 37.6, lng: 127.1 }]  ✅ 무효화됨
  ['library', 'list', { lat: 38.0, lng: 128.0 }]  ✅ 무효화됨
  ['library', 'detail', '123']                     ❌ 무효화 안됨

  언제 lists() vs list(params) vs all을 쓸까?

  ✅ lists() - 모든 리스트에 영향

  // 도서관 생성 후
  libraryMutations.POST_LIBRARY({
    onSuccess: () => {
      // 어떤 위치에서 보든, 모든 리스트를 갱신해야 함
      queryClient.invalidateQueries({ queryKey: queryKeys.library.lists() });
    }
  });

  ✅ list(params) - 특정 조건의 리스트만 영향

  // 특정 위치의 도서관만 북마크 추가 (다른 위치는 영향 없음)
  bookmarkMutation({
    onSuccess: () => {
      // 현재 보고 있는 리스트만 갱신
      queryClient.invalidateQueries({
        queryKey: queryKeys.library.list({ lat: 37.5, lng: 127.0 })
      });
    }
  });

  ✅ all - 리스트+상세 모두 영향

  // 도서관 삭제 시
  deleteMutation({
    onSuccess: () => {
      // 리스트에서도 사라져야 하고, 상세페이지도 갱신되어야 함
      queryClient.invalidateQueries({ queryKey: queryKeys.library.all });
    }
  });

  요약

  | 상황            | 사용할 키                | 이유                    |
  |---------------|----------------------|-----------------------|
  | 도서관 생성/수정     | lists()              | 모든 위치의 리스트가 영향받을 수 있음 |
  | 도서관 삭제        | all                  | 리스트 + 상세 모두 영향        |
  | 특정 위치 데이터만 갱신 | list(params)         | 해당 조건만 갱신             |
  | 도서관 상세 수정     | detail(id) + lists() | 해당 상세와 리스트 모두 갱신      |

  핵심: lists()는 "모든 파라미터 조합의 리스트들"을 무효화할 때 사용합니다!

accessToken, refreshToken 구조에 대한 이해

Access Token과 RefreshToken은 똑같은 JWT, 단지 ACctessToken은 접근, RefreshToken은 재발급에 관여하는 것 뿐 ⇒ 역할이 다르다

refresh는 유효기간이 긺

  1. access token, refresh token 둘다 만료 ⇒ 에러 발생, 재로그인 필요
  2. access token 만료, refresh token 유효 ⇒ refresh token 검증, access 재발급
  3. access token 유효, refresh token 만료 ⇒ access token 검증, refresh 재발급
  4. access token, refresh token 유효 ⇒ 동작

초기 로그인시 둘다 발급하고 위와 같은 절차에 따라 동작

보통 access token은 세션, refresh는 쿠키에 저장한다고 AI피셜

리액트에서 JS API 연결하기

  • useEffect를 통해서 API 다루고 그걸 window객체로서 접근해서 받는게 일반적
  • ts환경에서는 있으면 타입정의가 안되니 declare를 통해 사전에 정의해두자
interface KakaoMaps {
  maps: {
    load: (callback: () => void) => void;
    services: {
      Status: {
        OK: string;
        ZERO_RESULT: string;
        ERROR: string;
      };
      Geocoder: new () => Geocoder;
    };
  };
}

이런식

리액트 쿼리는 쿼리 키 비교할떄 참조값이 아닌 값의 깊은 비교를 통해 비교함 다른 참조값 같은 값 ⇒ 같은 키 임

리액트 쿼리를 사용할 때, 데이터 의존적 구조

리액트 쿼리로 A 데이터 받고 그 A 데이터에 의존한 B 데이터 다시 받아오는 구조는 안전한가?

⇒ 응 안전해~ 리액트 쿼리는 의존 요청 패턴 처리하도록 설계되어 있어

다만 enabled 옵션을 반드시 활용해야함, 키 값이 준비될떄까지 API 실행을 막기떄문

메모이제이션은 언제 사용할까?

1) 자식에게 props 전달됨?

➡ 잠재적으로 useCallback 필요.

2) 그 자식이 렌더링 비용이 큰 컴포넌트인가?

(BottomSheet, Modal, Item 리스트, 차트 등)
➡ useCallback 사실상 필요.

3) 자식이 React.memo 등을 사용할 가능성이 있는가?

➡ 향후 유지보수/확장까지 고려하면 useCallback 추천.

4) 단순한 자식이고 memo로 감쌀 일도 절대 없고 렌더링도 가볍다?

➡ 필요 없음.

메모이제이션은 말 그대로 usCallback, useMemo와 같이 각각 함수와 값을 캐싱해서 의존값이 달라질 때 까지 요청을 최소화 해 최적화 하는 것

작성한 함수라 프롭스로 전달돼? 그러면 useCallback 준비, 그리고 그게 렌더링이 크다? useCallback 사용

자식이 useMemo를 사용한다?, 그러면 useCallback

암기 하지말고 이해 하자.