오늘은 리액트 쿼리에 대해 공부했다!

지금 하고 있는 프로젝트에서는 리덕스 툴킷을 사용해 왔는데,
로그인 부분을 맡고 있는 팀원 분이 리덕스 툴킷을 쓰면 너무 복잡해진다고 '리코일'을 쓰자고 제안하셨다.

회의 결과,

리덕스 툴킷 --> 리액트 쿼리 --> 필요한 부분에 한해 리코일 대체

로 코드를 짜기로 결정 됐다 !

그래서 오늘은 리액트 쿼리, 내일은 리코일 공부를 하기로 했다.

리액트 쿼리를 공부하던 중 구조분해할당 이라는 것도 알게 되어 같이 정리해보려고 한다.


구조 분해 할당(Destructuring Assignment)

구조 분해 할당은 말 그대로 구조를 분해해서 할당하는 것을 이른다.

만약 test라는 객체가 있고, 그 안의 id를 불러오고 싶은 경우에는

이런 식으로밖에 불러오지 못할 것이다.
하지만 test 안의 id 라는 상당히 복잡한 구조로 불러내야 하기 때문에, 우리는 구조분해할당을 시도할 수 있다.

우변에 가져올 데이터의 이름 'test'를 작성하고,

좌변에는 중괄호를 쳐서 가져올 데이터를 넣는다.

이처럼 복수로 넣어도 되고 단일로 한 가지만 넣어도 된다.
이것이 구조분해할당이다.

구조분해할당을 한 경우,

console.log(test.id) 가 아닌,
console.log(id)로 불러올 수 있다.

구조분해할당은 이름을 붙일 수도 있다 !


React Query 에 대해

React Query ?

  • 서버 상태(server state)를 관리하는 라이브러리
  • 기본적으로 함수형 컴포넌트 안에서 훅 형태로 사용되며, 굳이 서버 상태를 다른 장소에 저장할 필요가 없음
    - 즉, 서버 데이터 처리와 관련된 redux 액션, 리듀서, 미들웨어 코드를 작성할 필요가 없어진다.
    • Redux는 훌륭한 상태 관리 기능을 제공하지만, 동시에 많은 양의 boilerplate 코드라는 피로감도 제공하고 있기에 주목할 필요가 있다.
  • 하지만 상태 관리 라이브러리를 사용해서 서버 데이터를 제어하는 쪽을 더 선호한다면 React Query는 도입하지 말거나 앱에서 꼭 필요한 부분에만 사용하는 편이 좋다.

React Query 에는 두 가지 함수가 있다.

  • Query : 자동 렌더링
  • Mutate : 조건형 렌더링

상황마다 다르지만 일반적으로 이런 식으로 사용한다.

Query : Read = get

Mutate : Create, Update, Delete = put, patch, delete

왜냐하면 Query 는 자동으로 렌더링(변경) 되는 useEffect 와 같은 성질이고,
Mutate 는 특정 조건을 이수해야만 발동되는 조건형 렌더링이기 때문이다.

--> 가끔 '아이디 중복 체크' 와 같은 '버튼을 클릭' 해야만 발동되게끔 Mutate에 get 요청을 보낼 때도 있다.


React Query 사용 방법

React Query를 통해 관리하는 쿼리 데이터는 라이프사이클에 따라 fetching, fresh, stale, inactive, delete 상태를 가진다.

  • fetching - 요청 중인 쿼리
  • fresh - 만료되지 않은 쿼리. 컴포넌트가 마운트, 업데이트되어도 데이터를 다시 요청하지 않는다
  • stale - 만료된 쿼리. 컴포넌트가 마운트, 업데이트되면 데이터를 다시 요청한다.
  • inactive - 사용하지 않는 쿼리. 일정 시간이 지나면 가비지 컬렉터가 캐시에서 제거한다
  • delete - 가비지 컬렉터에 의해 캐시에서 제거된 쿼리

1. QueryClientProvider, ReactQueryDevtools import 설정

  • QueryClientProvider

    • React Query 는 캐시를 관리하기 위해 QueryClient 인스턴스를 사용한다.
    • 쿼리 인스턴스를 생성 후 client={queryClient} 작성해준다
    • 리액트 쿼리 사용을 위해 QueryClientProvider 를 최상단에서 감싸주어야한다.
  • ReactQueryDevtools

    • React Query 의 모든 내부 작업을 시각화할 수 있다.
    • Query 데이터들을 바로 볼 수 있다.
    • 문제가 발생할 경우 디버깅 시간을 절약할 수 있다.

1-1. useQuery 로 서버데이터 가져오기

서버에서 데이터를 가져오고 캐싱을 하는데 사용하는 기본이며 가장 많이 사용하게 되는 훅이다.

useQuery 훅이 리턴하는 데이터와 옵션의 종류는 매우 다양하다. 주요 사항은 다음과 같다.

  • 리턴 데이터

    • data - 쿼리 함수가 리턴한 Promise에서 resolve된 데이터
    • isLoading - 저장된 캐시가 없는 상태에서 데이터를 요청중일 때 true
    • isFetching - 캐시가 있거나 없거나 데이터가 요청중일 때 true
  • 옵션

    • onSuccess - 쿼리 함수가 성공적으로 데이터를 가져왔을 때 호출되는 함수
    • onError - 쿼리 함수에서 오류가 발생했을 때

2-1. useQuery // isLoading

  • useQuery 를 구조분해할당
  • Query 의 첫 번째 인자 = Query name = unique key
    • 내부적으로 refetching, caching, query 공유를 위해 사용됨
    • 중복되지 않게 주의하기 !
    • api 요청으로 얻은 데이터를 구분할 수 있는 key를 부여해서 또 다시 같은 데이터를 요청하지 않도록 할수 있다.
    • useQuery의 key가 달라지면 곧바로 다시 api 요청을 한다.
    • 이름만 쓰거나, id 와 같은 자동렌더링 조건 추가 가능
  • Query 의 두 번째 인자 = 함수
    • axios 사용
    • data를 resolve 하거나 error를 뱉는 Promise 를 리턴하는 함수를 넣음
  • Query 의 세 번째 인자 = onSuccess, onError
    • 성공했을 경우 : onSuccess
      • onSuccess를 공백으로 남기면 query.data로 넘어옴
    • 실패했을 경우 : onError

삼항연산자를 이용해 isLoading 사용
구조분해할당을 사용했기 때문에 isLoading ? 만으로 적용 가능

만약 구조분해할당을 하지 않았다면?

query 의 isLoading, query 의 data 등으로 사용해야함 !

또한 로딩 중일 때는 객체,
로딩이 끝나면 배열이라 map을 돌릴 수 있다. (data를 불러왔기 때문)


2-2. useMutate // create

  • mutate 에 create 라는 이름을 붙였다.

  • query name 은 생략 가능하다.

  • axios post 요청은 동일하다.

  • onSuccess 로 data를 받아온다.

    • data를 보내주는 방법
  • queryClient.invalidateQueries 사용


2-2-1. queryClient.invalidateQueries

--> data를 refresh 할 때 사용

포스트 요청을 하거나 삭제 요청을 했을 때 화면에 보여주는 데이터에 변화를 줘야 한다.
그러나 query 키가 변하지 않으므로 강제 refresh 해야할 필요가 있다.
이런 때는 queryClient의 invalidateQueries 메소드를 이용해서 query 키를 날려버린다.

 queryClient.invalidateQueries("test");
 

2-2-2. 쿼리 무효화 invalidataQueries

invalidateQueries에 의해 invalidate(무효화)되면

  • useQuery가 사용하는 staleTime이라는 속성이 변경된다.
  • rendering도 다시 한다.
const queryClient = useQueryClient();

// 캐시에 있는 모든 쿼리를 무효화한다.
queryClient.invalidateQueries()

// todo로 시작하는 모든 쿼리를 무효화한다. ex) ['todos', 1], ['todos', 2], ...
queryClient.invalidateQueries('todos')

// ['todos', 1] 키를 가진 쿼리를 무효화한다.
queryClient.invalidateQueries(['todos', 1])

참조링크


2-3. useMutate // update

1번. usemutate 의 queryname은 생략 가능하다.
2번. mutate : modify 로 보내는 데이터는 id, data 값이다.

mutate의 첫 번째 인자에 값이 다 들어가야 하므로
보내야 할 데이터 종류가 2가지 이상이라면,
{ } 객체로 감싸서 한 개의 인자로 보내야 한다.

==> 2 번의 { id, data }

3번.

* 만약 onClick = { clickModify( ) } 라면 ?

  • 화살표 함수가 없으면 인자를 넘기지 못한다.
  • 클릭할 때가 아닌, 새로고침할 때 clickModify가 실행된다.
  • 실행하고 난 값이 onClick 으로 들어가기 때문에 undefinded 가 출력된다.

* 만약 onClick = { ( ) => clickModify( v.id ) } 라면 ? ( 정답 )

  • 화살표 함수를 사용해서 v.id 인자를 clickModify 로 넘겨주고,
  • const clickModify 에서 id 인자를 받아서 작성한다.

2-4. useMutate // delete


2-5. useMutate // image 1장 upload

멀티 이미지가 아닌 1장 이미지 업로드 기준이다.

여기까지는 리덕스 툴킷 사용했을 때와 같다.

modifyImage로 formData 와 config 데이터를 전달해줘야 하기 때문에 { } 중괄호로 감싸야한다.

중괄호를 감싸지 않아 500 에러가 났다 ㅠㅠ..

그리고 리덕스 툴킷과 다르게 리덕스에 저장할 필요가 없기 때문에

저런 식으로 imageUrl을 저장할 필요가 없다 !

이것도 몰라서 엄청 헤맸다 ..

html 까지 profileList.imgPath --> data?.imgPath 로 변경했다 !

리덕스 툴킷에서 리액트 쿼리로 변경 완료 ..!

0개의 댓글