[Redux-Toolkit] 3. 서버통신 react-toolkit-query

AREUM·2023년 11월 22일

Redux 상태관리

목록 보기
3/3
post-thumbnail

공부한 내용을 정리했다.

경쟁 도구 : react-query SWR

https://bit.ly/rtk-

장점

  • Redux와 함께 사용해도 좋지만, Redux와 상관없이 사용할 수 있도록 독자적으로 설계되어있다.
  • 하나의 api를 여러개의 컴포넌트에 사용해 관리해줄 수 있다.

RTK-Query 설치 & 설정

  1. Redux 없이 rtk-query 사용
    https://redux-toolkit.js.org/rtk-query/api/ApiProvider
  2. Redux와 함께 rtk-query 사용
    https://redux-toolkit.js.org/rtk-query/api/createApi

설치

npx create-react-app my-app —template redux
npx create-react-app my-app —template redux-typescript

npm i @reduxjs/toolkit
yarn add @reduxjs/toolkit

RTK-Query 사용법 ( 화면 : client 입장 )

[ Server 팀에서 넘겨준 Server Hook 정보 ]

  • 상황
    1. React 잘함 O, RTK Query 못함 X
    2. 서버팀에서 서버랑 통신하는 Hook들을 RTK Query를 이용해 만들었다.
    3. RTK Query를 이용할 순 없어도 개발은 할 수 있다.

  • 구조

    • 화면 : App.js안에 모든 것이 다 들어있다.
    • 데이터API : RTK Query를 이용해 만든 Hook을 사용한다.
      ( /src/app/api.js )
  • Server Hook 사용법
    1. “./app/apiapi 객체를 이용하세요.”
    2. RTK Qeury로 서버와 통신하고 있습니다.
    3. 읽기 : useGetCountQuery({ name })
    4. 쓰기 : useSetCountMutation()

  • RTK Query의 특징

    • “use ~ Query”읽기전용
      • 서버와 통신 시, 자동으로 실행된다.
      • data, isFetching, isLoading 중요 - !

    • “use ~ Mutation”쓰기전용
      • 서버의 데이터를 변경할 때, 사용되고 실행한다.
      • use ~ Mutation() Hook을 사용하면, 배열을 return 한다.
      • 첫 번째 원소가 함수인데, 그 함수를 호출하면 그때 서버로 데이터를 전송한다.
      • 첫 번째 원소 함수 ({ name, value })
      • isLoading 중요 - !

사용법

[ app.js 전체 코드 ]

import React from "react";
import { api } from "./app/api";
const Count = ({ name }) => {
  const query = api.useGetCountQuery({ name });
  const mutation = api.useSetCountMutation();
  const setCount = mutation[0];
  if (query.isLoading) {
    return <>Loading</>;
  }
  return (
    <div>
      <button
        onClick={async () => {
          await setCount({ name, value: query.data + 1 });
        }}
      >
        {mutation[1].isLoading ? "updating..." : ""}
        {query.isFetching ? "fetching..." : ""}
        {name} {query.data}
      </button>
    </div>
  );
};
export default function App() {
  return (
    <>
      <Count name="egoing" />
      <Count name="egoing" />
      <Count name="jane" />
      <Count name="steve" />
    </>
  );
}

  1. app.js에서 console.log(api);를 찍어 만든 api hook을 확인한다.

  2. Count라는 컴포넌트에 파라미터로 name을 받아온다.
    const query = api.useGetCountQuery({ name });

  • data : 서버에서 받아온 데이터 프로퍼티
  • isLoading : 현재 서버에서 데이터를 가지고 오는 지를 블리언 값으로 확인
    ( false면 가지고 왔음을 의미 )
  • isFetching : 현재 서버에서 데이터를 읽어오고 있는지를 블리언 값으로 확인
    ( flase면 읽기 전을 의미 )
  • status : 어떤 작업이 진행 중인지 나타나준다.

  • Fetching이 다 되고 데이터를 다 가져왔을 때, dataundefind에서 필요한 값을 받는다.
  • statusfulfilled로 바뀐 것을 보면 ?
  • isLoading, isFetching 모두 false로 바뀌었다.
  • 로딩이 끝났으니, data10으로 바뀐것을 확인 할 수 있다.
    ( 서버에서 받은 데이터 값이다. )

use ~ Query사용시 isLoading or isFetching의 차이점

  • isLoading : 최초 한번만 실행된다. ( 컴포넌트를 초기화할 때 사용 )
  • isFetching : 서버와 통신을 할 때마다 실행된다. ( 로딩할 때 마다 사용 )
  • 결론 👉🏻 isLoading보다 isFetching을 더 많이 사용하는 편이다.
  1. 로딩중일 때,
    if ( query.isLoading ) return <>Loading</>;
  2. 데이터를 받아왔을 때,
<button>
  {query.isFetching ? “fetching...: “ “} {name} {query.data}
</button>
  1. useSetCountMutation() Hook을 사용한다.
    `const mutation = api.useSetCountMutation();

use ~ Query or use ~ Mutation의 차이점

  • use ~ Query
    • 서버와 통신 시, 자동으로 실행된다.
    • 객체를 return 한다.
  • use ~ Mutation은 쓰기전용
    • 첫 번째 원소가 함수인데, 그 함수를 호출하면 그때 변경된 데이터가 서버로 전송한다.
      ( 수동실행 )( promise로 응답 값을 전달 )
    • use ~ Mutation() Hook을 사용하면, 배열을 return 한다.
    • isFetching이 없고, 서버와 통신할 땐 무조건 isLoading을 사용한다.
    • 두 번째 원소 객체안에 isLoading이 있다.
  1. 첫 번째 원소인 함수를 변수에 담아준다.
    const setCount = mutation[0];
  2. 함수를 호출할 때, 데이터를 서버에 전송 시키는데 서버팀에서 함수사용법을 알려준 방법인
    name, value 프로퍼티를 사용하라고 알려줬다.
<button onClick={() => {
  setCount({ name, value: query.data + 1 });
}}>
  // .. 생략
</button>
  1. 결과는 개발자도구에 NetWork를 확인하면,
    POST로 전송되고 받아오는 value값이 기존 10인 숫자에서 11로 변경된 값을 확인할 수 있다.

별개로 경우에 따라서 서버가 응답한 결과를 바로! 즉시 ! 받아보고 싶을다면 ?
: promise로 응답 값을 전달할 수 있다.

<button onClick={ async () => {
  const result = await setCount({ name, value: query.data + 1 });
  console.log( ‘result’, result );
}}>
  // .. 생략
</button>

onClick을 했을 때,
서버에서 돌려준 데이터의 값인 result { data: 11 }console.log()에 찍히는 걸 확인할 수 있다.

  1. 쓰기 전용인 Mutation의 데이터인 isLoading을 조건을 걸어준다
<button>
  { mutation[1].isLoading ? “Updating…” : ““ }
  {query.isFetching ? “fetching...: “ “}
  {name}{query.data}
</button>

[ 코드의 흐름 ]
1. <>Loading</> ( use ~ Query : 읽기전용 )
2. 버튼 클릭 시, updating… ( use ~ Mutation : 쓰기전용 )
3. 바로, fetching… ( use ~ Query : 읽기전용 )
4. 마지막으로 { name } { query.data } : 데이터 값

RTK-Query Hook 만들기. ( data : server 입장 )

  • 예로 좋아요 버튼을 클릭 했을 시, 비동기로 서버와 통신해야할 때
/* api.js */
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const api = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: "https://example.com/api" }),
  tagTypes: ["Count"],
  endpoints: (builder) => ({
    getCount: builder.query({
      query: ({ name }) => `count/${name}`,
      providesTags: (result, error, arg) => {
        console.log(result, error, arg);
        return [{ type: "Count", id: arg.name }];
      }
    }),
    setCount: builder.mutation({
      query: ({ name, value }) => {
        return {
          url: `count/${name}`,
          method: "POST",
          body: { value }
        };
      },
      invalidatesTags: (result, error, arg) => [{ type: "Count", id: arg.name }]
    })
  })
});

// 자동생성되어 app파일에서 사용할 수 있다.
export const { useGetCountQuery } = countAPI
export const { useSettCountMutation } = countAPI
  • createApi : 서버와 통신할 url주소를 지정할 때, 사용
    ( 만약, 서버주소가 다르다면 다른 갯수만큼 만든다. )
  • endpoints : 가져오고, 추가하고, 수정하고, 삭제하기 위해 사용
    ( API를 문서로 만든 url을 보며 GET, POST, PATCH, DELETE 등의 메소드를 사용한다. )
  • getCount : 서버캐시의 이름
    • getCountbuild.query를 호출하면, getCount는 서버에서 어떻게 가져오는지의 정책이라고 한다.
      ( 커스텀이 아닌, 정해진 룰이다. )
    • getCount를 호출하면 반드시, query프로퍼티를 적어줘야한다.
    • query프로퍼티에는 함수가 따라오는데, 함수안에 url프로퍼티가 return값으로 온다.
      ( 전달받은 name값을 적어준다. )
    • url의 경로로 서버가 접속해 데이터를 가져와 그 데이터를 getCount -> query의 콜백인자의 캐시에 저장한다.
  • setCount : 서버캐시의 이름
    • getCount에서 사용한 방법과 비슷하다.
    • build.query 대신, build.mutation을 호출한다.
    • 그럼, createApi가 자동으로 useSetCountMutation이라는 Hook을 생성해준다.
    • query프로퍼티로 함수를 지정하는데, app에서 호출할 때 사용한 값인 name, value을 지정해준다.
      ( 코드를 짜면서 어떤 데이터를 전달해야할지를 생각하면 될거 같다. )
    • return값으로는 url, method, body를 지정해준다.
      ( 서버에 전송해줘야할 값들을 보내줘야한다. )
  • useGetCountQuery : 캐시의 데이터를 저장할 인터페이스이다.
    ( 사용할 이름을 지정해준 것이다. 앞에 use, 뒤에 Query가 붙으며, 첫글자는 대문자GetCount가 된 것으로 createApi를 사용하면 자동생된 것이다. )
  • 서버캐시의 업데이트 정책을 정해보면,
    createApi가 서버캐시를 갱신할 수 있는 Hook을 자동으로 만들어 주는 제너레이터라고 기억하자.

auto fetching

( Hook만들기 설명에 빠진 나머지 것들의 설명 )

데이터 흐름
버튼을 클릭 시 👉🏻 서버에 데이터가 보내짐 POST 👉🏻 서버에 최신의 데이터를 받아옴 GET
: But, 밑의 태그들을 사용하지 않으면 !
서버에 데이터를 잘 보냈지만, 최신의 데이터를 가져오지 못하는 상황이 발생한다.

  • tagTypes : 해당 apiType name을 지정해주어야한다.
    원래는 데이터를 가져오는 코드가 필요하지만, rtk-query에선 tagTypes가 자동으로 가져오는 역할을 한다.
  • providesTags를 사용하면,
    getCount로 파생된 useGetCountQuery() Hook이 실행되면 providesTage태그가 만들어 낸 tags와 동일한 서버 Cache가 자동으로 생성되면서 지정한 반환값이 생성된다.
  • invalidatesTags를 사용하면,
    setCount로 파생된 useSetCountMatation() Hook이 실행이 되면 invalidatesTags태그가 만들어 낸 tags와 동일한 서버 Cache가 자동으로 삭제되고 서버에서 가져오는 절차를 자동으로 밟게 된다.

상황

업로드중..

데이터 가져오기
1. Server/count라는 1이라는 값이 있다고 가정해보자.
2. Client에 여러개의 컴포넌트 중 하나에 컴포넌트 안에 useGetCountQuery() Hook을 사용한다.
그 말은 즉, GetCount라는 Server쪽 데이터를 저장하는 Cache가 존재한다.
3. 개발자가 useGetCountQuery()를 사용한다는 말은 GetCount라는 Cache를 구독하고 있다는 말을 뜻한다.
변경사항을 받는 것을 의미한다.
4. useGetCountQuery()가 존재하고, 사용하는데 GetCount라는 Cache안에 내용이 비어있을 경우, RTK-query는 자동으로 Server에 접속해 데이터 ( 1 )를 가져와 Cache에 데이터 ( 1 )를 채워넣는다.
5. 그럼, Cache를 구독하고 있던 컴포넌트들이 전부, 새롭게 렌더링되면서 새로운 값을 가져가 화면에 그려준다.
6. 만약, 또 다른 컴포넌트에도 useGetCountQuery()를 사용한다면, 그 컴포넌트에 데이터 값이 자동으로 저장된다.

데이터 업데이트
1. useUpdateCountmutation() Hook을 실행시켜 Server쪽의 데이터를 2로 값을 변경했다고 가정해보자.
Server쪽에 데이터를 바꿨다고 한들, Client데이터도 바뀐다고 확정 지을 순 없다.
2. createApi를 사용해 Hook을 만들 때, 사용할 태그명의 이름을 지정해준다.
3. useUpdateCountMutation()을 실행시키면 서버의 데이터를 변경시키고 난 후 ( 2 ), counter라는 동일한 이름을 가지고 있는 cache들을 모두 지우는 일을 자동으로 해준다.
Client에 저장된 데이터인( 1 )을 모두 지운다.
4. Cache에 모두 빈 데이터가 되면, 서버에서 새로운 값을 가져온다. ( 2 )
5. Cache의 데이터가 바뀌었고, 채워졌으니 Cache를 구독하고 있는 컴포넌트들에 데이터가 자동으로 렌더링되어 채워진다.
6. 그걸 가능하게 해주는 것이 providesTags, invalidatesTags이다.

  • getCount => providesTags : 제공하다.
    ( 생성 )
  • setCount => invalidatesTags : 무료화 되다.
    ( 제거 )( 서버Cache를 무효화한다. )
    : return 값으로 1, 2, 3번 중 하나를 이용해 태그의 어떠한 형태를 지정해줘야한다.
    • result : 서버에서 가져온 데이터 값
    • error : 서버와 통신했을 때, 에러가 나면 에러의 값이 호출
    • arg : 서버에서 useGetCountQuery를 호출할 때 파라미터로 들어간 값
      ( name )
providesTage : ( result, error, arg ) => {
	return [{ type: ‘Count’, id: arg.name }]
}

Count라는 typeidname이 생긴 것이다.

지금 현재의 상태관리의 보안해야할 점이 있다.
store는 페이지를 새로고침 할 경우 state가 날아가는 상황이 발생한다.

이것에 대한 대응 방안으로 localStorage 또는 session에 저장하고자 하는 reducer state를 저장하여, 새로고침 하여도 저장공간에 있는 데이터를 redux에 불러오는 형식redux-persist을 사용해보자.

redux-persist의 사용법을 테스트하며 공부해보고 정리해서 올려보도록 하겠다.

❗️요즘은 복잡한 구조의 상태관리나 코드가 길어져버리는redux의 상태관리를 많이 사용을 줄인다고 한다.
가볍고, 간결한 Zustand를 사용해 클라이언트를 관리를 하고,
서버는 react-query를 사용해 서버의 상태를 관리하는 방법도 있다.

profile
어깨빵으로 부딪혀보는 개발끄적이는 양씨 인간

0개의 댓글