[내일배움캠프 TIL] 49일차

Jaehyeon Ye·2023년 1월 5일
0

오늘 새로 배운 것

Server State, Server State 하는데 Server State가 뭐지?

API 통신으로 서버에 요청했을 때 로딩 중이라면 isLoading(true, false), 통신에 실패했을 때는 error, 통신에 성공하여 서버로부터 data를 응답받는 것

react query를 적용하기 전 thunk를 사용했을 때는,

const postSomething = {
 	[postSomething.pending]: (state, action)=>{
    	state.isLoading = true;
    },
  	[postSomething.fulfilled]: (state, action)=>{
    	state.postSomething = action.payload;
      	state.isLoading = false;
    },
  	[postSomething.rejected]: (state, action)=>{
    	state.error = action.error;
      	state.isLoading = false;
    },
}

이런 식으로 관리를 해줬었는데 React Query에서는

const fetcher = () =>
     fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
       res.json());

const { isLoading, error, data } = useQuery('repoData', fetcher)

React Query의 useQuery 훅에서 자동으로 서버 상태값을 리턴받도록 간결하게 코드를 쓸 수 있다.

React Query를 적용함으로서 얻는 이점과 캐싱

react query를 적용함으로써 코드 가독성 부분에서도 더 좋아지고, 캐싱이라는 개념을 통해 불필요한 서버 요청도 알아서 방지해준다.

캐싱이라는 개념은 바로 위의 코드를 예시로 들면, 서버 요청을 보냈을 때 repoData라는 Query Key를 보내는데, Query Key를 검색한 뒤 Cache Memory에 Key가 있는지 확인하여 있으면 fetcher 함수를 실행하지 않고 Cache Memory에 있는 데이터를 그대로 사용하고, 만약 없다면 fetcher 함수를 실행하여 받아온 결과를 Key에 대응하는 Value에 저장한다.

로컬 스토리지도 key, value 형태로 되어있는데

localstorage(repoData, fetch 결과)

서버에서 받아온 response 결과가 key에 해당하는 value로 저장한다.

React Query 적용

App.js에 QueryClient, QueryClientProvider를 import 해온다. 그리고 QueryClientProvider를 jsx안에서 외부에 감싸주고 client로 queryClient를 prop으로 넘긴다.
queryClient는 QueryClientProvider 안의 모든 컴포넌트 내에서 저장되어있는 캐시 메모리에 직접 접근할 수 있는 관리자 역할을 한다.

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      ...
    </QueryClientProvider>
  );
}

useQuery를 필요한 컴포넌트에 import해주고 fetch하는 부분에 setState 함수도 제거하는 등 필요한 리팩터링해준다.

import { useQuery, useQueryClient } from 'react-query';

//함수 내부
const queryClient = useQueryClient();

// As is
// const getNowPlaying = async () => {
//    const { results } = await fetch(
//      `${BASE_URL}/now_playing?api_key=${API_KEY}&language=${LANG[0]}&page=1`
//    ).then((res) => res.json());
//    setNowPlayings(results);
  };

// To be
export const getNowPlaying = () =>
  fetch(
    `${BASE_URL}/now_playing?api_key=${API_KEY}&language=${LANG[0]}&page=1`
  ).then((res) => res.json());

useState가 관리하던 Server State는 useQuery가 관리하게 되므로 해당 부분을 제거하고 useEffect로 getData해오던 부분도 useQuery 쪽에서 맡게 되었으므로 제거한다.

//As is
  // const [nowPlayings, setNowPlayings] = useState([]);
  // const [isLoading, setIsLoading] = useState(true);

  // const getData = async () => {
  //   await Promise.all([getNowPlaying(), getTopRated(), getUpcoming()]);
  //   setIsLoading(false);
  // };

  // useEffect(() => {
  //   getData();
  // }, []);

//To be
const {
    data: nowPlayingData,
    isLoading: isLoadingNP,
  } = useQuery(['Movies', 'NowPlaying'], getNowPlaying);

alias를 통해 data와 isLoading의 별칭을 지정한다.

Refetch하는 부분도 Promise.all 대신에 queryClient를 사용해 Refetch를 한꺼번에 받아오도록 리팩터링해준다. 이때 기존의 Promise.all에서 배열 요소로 서로 다른 Query Key를 받는 경우 queryClient의 refetchQueries를 적용하기 위해서는 공통된 Query Key를 받도록 리팩터링 해주어야한다. Query Key는 문자열도 되지만 배열로도 받을 수 있다. 그래서 useQuery의 기존에 있던 Query Key에 Movies라는 공통된 Query Key를 추가하여 받고 queryClient에 공통된 Query Key를 넣어주면 이 키를 가진 fetcher 함수들이 일괄적으로 refetch 실행을 하도록 할 수 있다.

 const {
    data: nowPlayingData,
    isLoading: isLoadingNP,
    refetch: refetchNP,
  } = useQuery(['Movies', 'NowPlaying'], getNowPlaying);
...

 const onRefresh = async () => {
    setIsRefreshing(true);
    //refetch
    // await Promise.all([refetchNP(), ...]);
    await queryClient.refetchQueries(['Movies']);
    setIsRefreshing(false);
  };

또한 컴포넌트의 어떤 고유의 요소에 대한 상세페이지에 접근할 때 param값을 통해 id를 받아야하는데

//상세페이지 컴포넌트
import { getDetail } from '../api';

export default function Detail({
  navigation: { navigate },
  route: {
    params: { movieId },
  },
}) {
...
const { data, isLoading } = useQuery(['Detail', movieId], getDetail);
...
}

//api 컴포넌트
export const getDetail = (params) => {
  const [_, movieId] = params.queryKey;
  return fetch(
    `${BASE_URL}/${movieId}?api_key=${API_KEY}&language=${LANG[0]}&append_to_response=videos`
  ).then((res) => res.json());
};

useQuery의 getDetail이라는 fetcher 함수가 실행될 때 useQuery의 queryKey가 매개변수로 넘어간다. 이를 params로 넘겨받을 수 있다.

profile
FE Developer

0개의 댓글