새 블로그 개발일지 - 4: react-query

react-query 를 이용하여 포스트 목록을 표시해보자

데이터 연동하기

시리즈 1편시리즈 2편 에서 미리 데이터 연동에 대한 준비를 했었다. 서버는 준비되어 있는 상태이고 클라이언트와 서버 사이에 연결만 해주면 된다. 이 부분은 react-query 를 이용할 생각이다.
시리즈 3편 이후로 따로 포스트에는 적지 않았지만 꽤 많은 부분이 이미 개발되었다. 예를들어 리덕스 모듈이나 스타일 테마 등 사실 이전에 블로그에 꽤 포스팅을 했었고, 다른 블로그에서 더 상세한 가이드가 많다. 어찌됐든 이 포스트는 가이드가 아니라 개발 일지 일뿐이니 생략하려고 한다. 나중에 기회가 되면 스타일 테마는 따로 포스트로 적어도 괜찮을것 같다.
 

react-query

yarn add react-query
최근에 클라이언트와 서버사이에 안정적인 통신을 하기 위해 쓰는 꽤 핫한 라이브러리이다.
유저는 클라이언트와 서버 사이를 연결하는 일종의 규칙? 정도만 작성해주면 실제로 필요한 많은 부분들을 담당해준다. 예를들어 비동기처리나 에러핸들링이나 이러한 부분을 정의만 해주면 알아서 처리해주기 때문에 꽤 많은 사람들이 이용하는듯 하다.
이 포스트에서는 아주 기본적인 내용만 적으려고 한다. 실제 코드는 약간 더 복잡할 수 있지만 이 블로그 깃헙에서 확인하길…
 
// pages/_app.tsx const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false // onError: handlerError }, mutations: { // onError: handlerError } } }); export default function App({ Component, pageProps }: AppProps) { return ( <Provider store={store}> <PersistGate persistor={persistor} loading={null}> <QueryClientProvider client={queryClient}> <ThemeProvider theme={theme}> <DefaultLayout> <LayoutInner> <Component {...pageProps} /> </LayoutInner> </DefaultLayout> </ThemeProvider> </QueryClientProvider> </PersistGate> </Provider> ); }
지난번 코드와 비교하면 꽤나 많은 부분이 변했지만 생략한다.
react-query 를 사용하기 위해서는 일단 QueryClientProvider 로 앱을 한번 래핑해준다.
 

api 선언하기

// core/api/posts.ts import { PageObjectResponse } from '@notionhq/client/build/src/api-endpoints'; import { request } from './'; export const postApis = { getPosts: (params = {}) => request<PageObjectResponse[]>({ url: '/posts', method: 'GET', params }) };
core 폴더에 api 에 posts api를 작성해준다. 이것도 비약적으로 생략되긴 했지만, axios 를 이용하여 선언해둔 모습이다. request 함수는 자신의 입맛에 맞게 커스텀이 가능하다. 단지 return 이 axios request 일 뿐이다.
 

query 작성하기

일반적으로 react-query 의 가장 좋은 이용사례는 api마다 커스텀훅을 만들어 사용하는것이다. 일반적으로 react-query 는 쿼리 키 라는 개념이 있는데, 이 쿼리 키로 react-query 의 데이터의 유효성을 조절할 수 있다. 데이터의 유효성이 사라지면 react-query 는 기본적으로 데이터를 다시 새롭게 가져온다.
// core/queries/posts.ts import { useQuery, useQueryClient, UseQueryOptions } from 'react-query'; import { postApis } from '../apis/posts'; import type { PageObjectResponse } from '@notionhq/client/build/src/api-endpoints'; import type { Response } from '../../interfaces'; export const postsQueryKey = { posts: () => ['posts'] as const }; export const useGetPosts = ( options?: UseQueryOptions<Response<PageObjectResponse[]>> ) => { return useQuery( postsQueryKey.posts(), () => postApis.getPosts(), options as any ); };
일단은 이렇게 커스텀 훅을 만들었다.
 
// components/Posts/Posts.tsx import { PageObjectResponse } from '@notionhq/client/build/src/api-endpoints'; import { useGetPosts } from '../../core/queries/posts'; import type { Response } from '../../interfaces'; interface IPostsProps { initialPosts: Response<PageObjectResponse[]>; } const Posts: React.FC<IPostsProps> = ({ initialPosts }) => { const { data } = useGetPosts({ initialData: initialPosts }); return ( <div className="posts-wrapper"> <ul>{}</ul> </div> ); }; export default Posts;
이런식으로 위에서 정의한 useGetPosts 를 사용한다. 여기서 주의할 점이 있는데 initialPosts 라는 데이터이다. 만든 커스텀훅에 옵션으로 initialData 에 넣어주게 되어있는데, 나는 블로그로써의 기능을 하기 위해 SSR을 지원해야 하기 때문에 서버단계에서 이미 페이지가 생성된다. 그 때 필요한 데이터가 바로 initialData 이다.
 

SSR 설정하기

// pages/index.tsx import Home from '../components/Home'; import { postApis } from '../core/apis/posts'; import type { Response } from '../interfaces'; import type { PageObjectResponse } from '@notionhq/client/build/src/api-endpoints'; export default function ({ data }: { data: Response<PageObjectResponse[]> }) { return <Home initialPosts={data} />; } export const getServerSideProps = async () => { const res = await postApis.getPosts(); return { props: { data: res } }; };
홈 화면에서 포스트를 바로 보여줄 것이기에 index.tsx 파일에서 작업한다.
아래쪽에 있는 getServerSideProps 를 이용하면 서버사이드에 필요한 데이터를 미리 가져올 수 있다. 사실 이 부분에서 에러처리나 404페이지로 리다이렉트 등 기능을 추가할 수 있다. 이 부분은 나중에 개발이 완료되고 나서 안정화 시킬때 하면 될듯?
 
주의사항으로 getServerSideProps 에서 fetch 하는 url은 항상 절대주소를 이용해야 한다. 이 코드는 서버사이드에서 실행되는데 상대주소를 이용하면 이것 때문인지 사용할 수 없다고 에러가 발생한다.
 

마무리

SSR은 어렵다 항상 CSR만 개발하다보니 생각치도 못한곳에서 에러가 발생한다. 나중에 이런 시행착오도 적어두면 도움이 되려나???