React-Query 알아보기(1)

이지훈·2022년 10월 30일
0
post-thumbnail

React-Query란?

서버 상태관리 라이브러리의 일환으로 서버로 부터 클라이언트로 값을 가져오거나 캐싱, 값 업데이트, 에러헨들링을 더욱 편하게 하는데 사용되는 라이브러리입니다.

Why use?

  1. 기존의 많이 사용하던 redux라이브러리는 클라이언트 상태관리 라이브러리 이다. redux를 이용하여 서버 상태관리를 하기 위해서는 redux-thunk나 redux-saga와 같은 라이브러리를 같이 사용해야하는데 그 구현이 너무 복잡하고 redux가 클라이언트 상태관리에 치중되어 있다보니깐 혼란이 가중되곤 했다.

  2. 보통 클라이언트에서 서버로부터 값을 받아오면 클라이언트 상태로 정장하게 된다. 그럴경우 값이 바뀌지 않았는데 해당 페이지로 가게 되면 또다시 해당 값들을 받아와야하는 경우가 생기게 된다.

  3. 값이 업데이트 할때, 기존의 경우 update를 해주는 refetch과정을 거치게 되어 put, patch 이후 바뀐값을 받기 위해 get을 다시 서버로부터 받아와야하는 비효율이 발생했다.

  4. JS는 한개의 쓰레드를 이용하여 동기적으로 실행되기 때문에 서버로부터 값을 받아오는것과 같은 비동기 작업을 할때는 세밀한 에러핸들링이 필요하다.

환경

NextJs + TypeScript(프론트엔드), NestJs(백엔드)

사용하기

react-query는 typescript를 지원하기 때문에 별도의 @types 라이브러리를 받지 않아도 됩니다.

npm install react-query

nextJs의 최상단 컴포넌트에 react-query설정을 합니다.

//_app.tsx

import type { AppProps } from "next/app";
import { QueryClient, QueryClientProvider } from "react-query";

export const queryClient = new QueryClient();

function MyApp({ Component, pageProps }: AppProps) {
    return (
        <QueryClientProvider client={queryClient}>
            <Component {...pageProps} />
        </QueryClientProvider>
    );
}

export default MyApp;

USEQUERY

데이터를 가져오기 위해서 react-query에서는 useQuery라는 메소드를 사용합니다.
useQuery 메소의 parmeter는 unique Key, api 호출 함수, options입니다.
unique key는 string 또는 배열이 들어갑니다.
string을 사용하면 다른 컴포넌트에서 쓸수 있는 key값이 되고, 배열을 사용하면 첫번째 요소는 key값 두번째 요소는 api 호출 함수에 전달 할 parameter값이 됩니다.

//index.tsx

const Home: NextPage = () => {
    const { isLoading, isError, data, error } = useQuery<ExamType>( //ExamType은 return한 data의 type
        "exam", //unique key
        fetData, //api 호출함수
        {
            refetchOnWindowFocus: false, //페이지에 되돌아왔을때 재실행 여부
            retry: 0, //실패시 다시 실행 여부
            onSuccess: (data) => {
                console.log("success", data);
            },
            onError: (error) => {
                console.log("error", error);
            },
        },
    );

    if (isLoading) {
        return <p>...loading</p>;
    }

    if (isError) {
        return <p>error!!!!!</p>;
    }

    return (
        <div>
            <Head>
                <title>Create Next App</title>
                <meta
                    name="description"
                    content="Generated by create next app"
                />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            {data?.map((item: any) => (
                <React.Fragment key={item.name}>
                    <p>name : {item.name}</p>
                    <p>email : {item.email}</p>
                </React.Fragment>
            ))}
        </div>
    );
};

export default Home;

return 값중 isLoading과 isError는 state로 바꾸어서 사용가능합니다.

api 호출 함수

export const fetData = async () => {
    const url = `http://localhost:4000`;

    const res = await axios({
        method: "get",
        url,
    });

    return res.data;
};

실행화면

react-query는 비동기적으로 동작합니다. 즉, 여러개의 useQuery가 있다면 동시에 실행됩니다.
따라서 동시에 실행시키고 싶다면 options의 enable속성을 사용하거나 useQueries를 사용하면 됩니다.

USEMUTATION

데이터를 바꾸거나 보내기 위해서 react-query에서는 useMutation라는 메소드를 사용합니다.
useQuery 메소의 parmeter는 api 호출 함수, options입니다.

const Home: NextPage = () => {
    const { isLoading, isError, data, error } = useQuery<ExamType>(
        "exam",
        fetData,
        {
            refetchOnWindowFocus: false,
            retry: 0,
            onSuccess: (data) => {
                console.log("success", data);
            },
            onError: (error) => {
                console.log("error", error);
            },
        },
    );

    const putEmail = useMutation(putData, {
        onSettled: () => {
            console.log("end");
        },
        onSuccess: (data) => {
            console.log(data);
            queryClient.invalidateQueries("exam"); //값 변경 후 refetch실행
        },
        onError: (error) => {
            console.log(error);
        },
    });

    if (isLoading) {
        return <p>...loading</p>;
    }

    if (isError) {
        return <p>error!!!!!</p>;
    }

    return (
        <div>
            <Head>
                <title>Create Next App</title>
                <meta
                    name="description"
                    content="Generated by create next app"
                />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            {data?.map((item: any) => (
                <React.Fragment key={item.name}>
                    <p>name : {item.name}</p>
                    <p>email : {item.email}</p>
                </React.Fragment>
            ))}
            <button
                onClick={() => {
                    putEmail.mutate(); //mutation 함수 실행
                }}>
                email 변경
            </button>
        </div>
    );
};

export default Home;

값을 변경한 후 get을 다시 실행하기 위해서 invalidateQueries("unique key") 대신에 setQueryData("unique key",data)를 사용할 수 있습니다.
단, setQueryData는 mutation의 결과 값이 바뀐 data를 반환 할때만 사용이 가능 합니다.

api 호출 함수

export const putData = async () => {
    const url = "http://localhost:4000/modify/jihoon";

    const res = await axios({
        method: "put",
        url,
    });

    return res.data;
};

실행화면

간편하게 mutation 실행시 refetch를 동작시킬수 있다!

QueryCache

프로젝트안에 있는 useQuery를 전역적으로 관리 할 수 있다.

_app.tsx

export const queryClient = new QueryClient({
    queryCache: new QueryCache({
        onError: (error: any, query: any) => {
            console.log(error, query);
            if (query.state.data !== undefined) {
                alert("에러가 났어요!" + error.message);
            }
        },
        onSuccess: (data) => {
            console.log("success : ", data);
        },
    }),
});

Suspense

loading과 error의 상태를 감지해서 로딩바나 에러 화면을 보여주기 위해서 사용한다.
조금더 선언적으로 코드를 짤 수 있다는 장점을 가진다.

global

export const queryClient = new QueryClient({
    queryCache: new QueryCache({
        onError: (error: any, query: any) => {
            console.log(error, query);
            if (query.state.data !== undefined) {
                alert("에러가 났어요!" + error.message);
            }
        },
        onSuccess: (data) => {
            console.log("success : ", data);
        },
    }),
    defaultOptions: {
        queries: {
            suspense: true,
        },
    },
});

local

 const { isLoading, isError, data, error } = useQuery<ExamType>(
        "exam",
        fetData,
        {
            refetchOnWindowFocus: false,
            retry: 0,
            suspense : true
        },
    );

적용

return (
<Suspense fallback={<p>...로딩중</p>}>
    {data?.map((item: any) => (
         <React.Fragment key={item.name}>				  				
              <p>name : {item.name}</p>
              <p>email : {item.email}</p>
          </React.Fragment>
     ))}
     <button
         onClick={() => {
                 putEmail.mutate();
 	  }}>
               email 변경
     </button>
</Suspense>
);

사실 NextJS에서 suspense를 위와 같이 React와 동일하게 사용을 하게 되면 에러가 발생하게 된다.
왜냐하면 NextJs는 SSR로 동작을 하기 때문이다.
특유의 hydration과정으로 인해 영향을 받는데 그 해결법은 다음시간에 알아보도록 하자.

오늘은 이렇게 react-query에 대해서 알아보았다.
아직 시작에 불과하니 추가적인 options나 useQueries, NextJs에서 suspense를 사용하는 방법에 대해서는 다음시간에 알아보도록 하자

참조

https://kyounghwan01.github.io/blog/React/react-query/basic/

profile
안녕하세요 주니어 프론트엔드 개발자 이지훈입니다.

0개의 댓글