티스토리 뷰

프로젝트

메인 프로젝트: react-query

네스사 2023. 7. 26. 19:20

이번 프로젝트에서 우리 팀은 react-query를 도입하기로 결정했다. 솔직히 인프런이나 다른 강의 사이트에서 해당 내용을 다루는 강의가 없거나, 해외에서 한 강의를 번역한 것뿐이라 별로 하고 싶지는 않았다. 

 

그러나 멘토 분의 강력한 추천과 실무에서 자주 사용된다는 것 때문에 이 생소한 라이브러리를 도입하였다. 솔직히 첫인상은 "어? 이거 그냥 axios함수 만들고, 거기에 넣으면 끝 아님? 개쉽네? 그리고 별 기능이 없어 보이는데? "라고 생각했다. 하지만 실제로는 유의미한 기능과 의외로 구현난이도가 조금 있었다. 솔직히 구현 난이도는 너무 생소한 라이브러리라서 그런 것도 있지만, 이후 설명할 "원티치 코드" 때문이기도 하다.

 

따라서 우선  react-query에 대한 간략한 설명과 실사용 예시와 후기, 그리고 방금 언급한  "원터치 사용"에 대해서 말해보고자 한다.

 

 

useQuery, useMutation


이 글은 후기, 회고의 일종이기에 

자세한 설명은 생략한다.

 

 react-query에 대해서 모르는 사람을 위해 간단하게 설명하자면, 우리가 주로 사용하는 axios요청을 인자로 받아서 캐시나 로딩 같은 옵션을 제공해 주는 hook 라이브러리이다. 이 라이브러리에서 가장 자주 사용되는 hook은 useQuery, useMutation으로, 보통 get요청 같은 데이터 조회는 useQuery를, post와 같이 서버에 저장된 데이터가 바뀌는 요청은 useMutation을 사용한다. 이 둘의 차이점은 쿼리키와 mutate에 있다.

 

 

 

쿼리키는 useQuery에서 첫 번째 인자로 들어가며, 서버에 보내는 요청에 일종의 별명을 붙이는 것이다. 모든 useQuery 각각의 고유한 쿼리키가 존재하며, 이 쿼리키를 이용해서 이전의 요청결과를 기억하는 캐싱으로 같은 요청이 오면 캐싱된 결과를 보여주는 기능이 있다. 이뿐만 아니라 다른 useQuery, useMutation에서 이 쿼리키를 사용하면, 요청 실행 전후에 자동으로 해당 useQuery를 실행하도록 해준다. 이 두 기능이 아마 react-query를 사용하는 대표적인 이유일 것이라 생각된다.

 

const { data = {} } = useQuery([queryKeys.likeShell, memberId], () =>
    getMyShellId(memberId)
  );
 const { data = {}, mutate } = useMutation(
    () => getMemberIdForDelete(shellId),
    {
      onSuccess: () => {
        queryClient.invalidateQueries(queryKeys.likeShell);
         //mutate에서 성공시 queryKeys.likeShell이라는 쿼리키를 가진 usequery가 실행된다.
      },
    }
  );

 

mutate는 일종의 버튼이라 생각하면 이해하기 쉽니다. 우선 useMutation은 hook이기에 함수 내부나 조건문에 사용될 수 없다. 그러나 만약 특정시기에 작동해야 하거나, 바디에 넣을 값이 사용자가 입력하거나 수시로 바뀔 경우, 이런 제약은 치명적이다. mutate는 그런 문제를 해결하기 위한 useMutation에서 반환되는 기본 메서드이다. 보통 구조 할당으로 useMutation에서 반환되며, 이를 사용해서 useMutation의 사용시기나 인자 값을 변경할 수 있다. 이것이 가능한 이유는 useMutation은 hook이라서 제약이 많지만 mutate는 반환값이 이기에 제약에서 상대적으로 자유롭다. mutate를 버튼이라고 한 이유도, mutate가 호출되면 useMutation의 요청이 실행되기에 그렇게 표현하였다.

  const deleteMutation = useMutation((postId) => deletePost(postId));
  <button
        onClick={() => {//클릭 시 사용되는 useMutation
          deleteMutation.mutate(post.id);
        }}
      >

 만약,  인자 값이 수시로 변동되거나 조건문으로 들어가는 변수를 달리해야 할 때는 mutate를 중심으로 함수를 만들 수  있다.

 const { mutate } = useMutation(
    (requestData: FormData) => getMemberId(id as string, requestData)
  );
  const handleUpdataMutate = (requestData: FormData) => {
    mutate(requestData); //useMutation선언 당시에 없던, 
//새로 생성되는 formData를 인자로 받게할수 있다.
  };
  
  const handleSave = () => {
    const formData = new FormData();
    const update = {
      password: newPassword,
    };
    formData.append(
      'update',
      new Blob([JSON.stringify(update)], { type: 'application/json' })
    );
    SendNewPassword(formData);//useMutation선언시 인자로 받은 id를 유지하면서
    //mutate에 필요한 데이터를 인자로 호출
  };

 

원티치 코드


원티치 코드은 개인적으로 내가 마음대로 부로는 호칭으로, 간단히 말해 비동기 요청 커스텀 hook을 의미한다. 멘토 분과의 조언으로 우리 팀은 모든 비동기 요청을 커스텀 hook으로 만들었다. 추가로 사용할 axios 요청을 hook과 같은 파일 내에서 선언하고, 비동기 통신 이후 나온 응답에 처리할 작업이 있다면 해당 hook에서 처리하여 비동기 파트와 컴포넌트를 효과적으로 분리할 수 있었다. 그 결과, 컴포넌트에서는 단순히 hook호출만 하고 html이나 이벤트 헨들러에 집중할 수 있었다. 또 같은 비동기 요청이 있다면 hook만 호출하면 된다는 장점도 있었다. 

 

난 지금까지 코드 중복을 최대한 줄이려고만 생각했지만, 이런 식으로 구현하면 axios 요청이나 react-query의 hook은 중복되지만 더 가독성이 좋고, 이후 다른 코드에 적용하기도 쉬워진다는 것을 알게 되었다.

 

아래는 로그인을 위한 hook으로, 예시를 위해 올렸다. 이런 식으로 만든 다면 막상 사용할 때는 usePostLogin만 호출하면 되므로 코드 한 줄로 구현할 수 있다. 이렇게 간단히 버튼 누르듯이 기능을 추가할 수 있어 원티치 코드라고 불렀던 것이다

 

import { useMutation } from 'react-query';
import { useNavigate } from 'react-router-dom';
import { axiosInstance } from '../../utill/axiosInstance';
import { useSetRecoilState } from 'recoil';
import { userStateWithExpiry } from '../../recoil/selector';

interface RequestBody {
  email: string;
  password: string;
}
const postLogin = async (requestBody: RequestBody): Promise<any> => {
  const response = await axiosInstance({
    url: `/auth/login`,
    method: 'post',
    data: requestBody,
  });

  return response;
};

export const usePostLogin = () => {
  const navigate = useNavigate();
  const setIsLoggedIn = useSetRecoilState(userStateWithExpiry);

  const { mutate } = useMutation(
    (requestBody: RequestBody) => postLogin(requestBody),
    {
      onSuccess: (res) => {
        setIsLoggedIn(true);
        const accessToken = res.headers.authorization;
        const id = res.data.id;
        const displayName = res.data.displayName;
        const profileUrl = res.data.profileUrl;
        const isMe = res.data.isMe;
        localStorage.setItem('accessToken', accessToken);
        localStorage.setItem('id', id);
        localStorage.setItem('displayName', displayName);
        localStorage.setItem('profileUrl', profileUrl);
        localStorage.setItem('isMe', isMe);
        navigate('/main');
      },
      onError(res: any) {
        if (res.response.status === 401) {
          alert('이메일과 비밀번호를 다시 확인해주세요.');
        }
      },
    }
  );
  return { mutate };
};

'프로젝트' 카테고리의 다른 글

메인 프로젝트: useEffect  (1) 2023.07.31
메인 프로젝트: recoil  (1) 2023.07.28
메인 프로젝트: 비동기 오류 중앙 제어  (0) 2023.07.04
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함