React Query를 이용해 ToDo List를 만들기 1

zaman·2022년 10월 1일
3

Toy Projects

목록 보기
3/6
post-thumbnail

출처
React Query Tutorial for Beginners
https://www.youtube.com/playlist?list=PLC3y8-rFHvwjTELCrPrcZlo6blLBUspd2


리액트 쿼리?

리액트에서 데이터를 fetching할 때 사용하는 라이브러리

왜 데이터를 fetching할 때 라이브러리를 사용할까?

리액트는 UI 라이브러리 따라서 데이터를 패칭할 때 특정 패턴을 가지고 있지 않음
보통 데이터를 패칭할 때 useEffect를, 컴포넌트 state를 유지(로딩, 에러 결과)할 때 useState를 사용함
그리고 만약 데이터가 여러곳에서 필요하다면 state management 라이브러리를 사용함
대부분의 state management 라이브러리는 client state와 잘 작동함 그러나 비동기 또는 server state와는 잘 맞지 않음
(state management libraries are not great for working with asynchronous or server state)

→ client state와 server state가 매우 다르기 때문!

Client state

: 앱 메모리에 유지되며 동기적으로 접근, 업데이트함

Server state

: 원격으로 유지되며 접근 또는 업데이트를 위해 비동기 API가 필요함

  • has shared ownership 공유 소유권을 가짐
  • data can be updated by someone else without your knowledge 내가 모르는 사이 다른 사람이 데이터 업데이트 가능
  • UI data may not be in sync with the remote data UI 데이터가 remote data와 싱크(동기)되지 않을 수 있음

challenging when you have to deal with caching, deduping multiple requests for the same data, updating stale data in the background, performance optimizations etc
이는 특히 캐싱, 동일한 데이터에 대한 중복 요청을 제거할 때, background의 오래된 데이터를 업데이트 할 때, 성능 최적화를 처리할 때 등 어려울 수 있음

이걸 일일히 하려면 많은 시간과 노력이 필요함 따라서 다 해주는 라이브러리를 통해 시간과 노력을 아끼자!

🛫 Project Setup

step 1) CRA로 새로운 리액트 프로젝트를 만든다
step 2) 앱에서 사용할 모의 데이터를 제공하는 API 엔드포인트 설정
step 3) 리액트 라우터와 application routes 설정
step 4) 전통적인 방법인 useEffect, useState로 데이터 패칭(비교용!)

https://github.com/gopinav/React-Query-Tutorials 여기서 클론해서 사용하면 된다고 함
나는 기존에 하던 프로젝트가 있어서 거기에 적용해보려고 한다

만약 이 튜토리얼을 따라 만들고 싶다면
npm i json-server or yarn add json-server
그리고 프로젝트 root에
db.json 파일을 만들어준다 그리고 여기에 api 데이터로 쓸 데이터를 넣어주면 됨

마지막으로 package.json의 scripts에 "serve-json" : "json-server --watch db.json --port 4000" 을 입력하면 끝이다

이제 터미널에 yarn serve-json을 입력하면 로컬호스트 포트 4000에서 api endpoint data를 확인할 수 있다

step2, 3은 생략 자세한건 영상을 참조하길 👍



🧐 사용해보기

코드가 긴 관계로 관련 없는 부분은 생략했다

useEffect, useState를 사용하는 전통적인 방법

//... import 생략

const Todos = () => {
  const token = localStorage.getItem("token") || "";
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [list, setList] = useState<IList[]>([]);
  const navigate = useNavigate();

  useEffect(() => {
    ToDoAPI.getTodos(token).then((res) => {
      setList(res.data.data);
      setIsLoading(false);
    });
  }, [token]);


  if (isLoading) {
    return <h2>Loading...</h2>;
  }

  return (
    <Container id="top">
      <Content>
        <section>
          {list.map((list, idx) => (
            <TodoList
              list={list}
              key={idx}
              id={list.id}
            />
          ))}
        </section>
      </Content>
    </Container>
  );
};

리액트 쿼리를 사용

먼저 root component인 App.tsx에서 설정을 해줘야 한다

// ... import 생략
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; // 리액트 쿼리의 개발도구

const querClient = new QueryClient();

function App() {
  return (
    <React.StrictMode>
      <QueryClientProvider client={querClient}>
        <BrowserRouter>
          <Routes>
           // routes 생략
          </Routes>
        </BrowserRouter>
              <ReactQueryDevtools initialIsOpen={false} />
      </QueryClientProvider>
    </React.StrictMode>
  );
}

export default App;

이제 react query가 제공하는 모든 훅과 기능을 사용할 수 있다

Todos.tsx

import { useQuery } from "@tanstack/react-query"; //1

const Todos = () => {
  const token = localStorage.getItem("token") || "";

// 나는 리액트 쿼리를 사용하는 부분을  분리해주었다
// 이 부분은 원티드 챌린지 코치님 코드를 참조함
  const Keys = {
    all: ["todos"] as const,
    details: () => [...Keys.all, "detail"] as const,
    detail: (id: string) => [...Keys.details(), id] as const,
  };

// destructuring
  const { isLoading, data } = useQuery(
    Keys.all,
    () => ToDoAPI.getTodos(token).then((response) => response.data));

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

  return (
    <Container id="top">
      <Content>
        <section>
          {data ? (
            data?.data.map((list: IList, idx: number) => (
              <TodoList
                list={list}
                key={idx}
                id={list.id}
              />
            ))
          ) : (
            <div>todo가 없습니다!</div>
          )}
        </section>
      </Content>
    </Container>
  );
};

useQuery는 2개의 argument(매개변수)를 필요로한다

  • 유니크 키(쿼리를 identify할 중복되지 않는키)
  • Promise를 반환하는 함수

이렇게 리액트 쿼리를 사용하면 useEffect, useState 없이 구현할 수 있다

리액트 쿼리로 데이터 패칭 에러 다루는 법

전통적인 방법

//... import 생략

const Todos = () => {
  const token = localStorage.getItem("token") || "";
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string>("");
  const [list, setList] = useState<IList[]>([]);

  useEffect(() => {
    ToDoAPI.getTodos(token).then((res) => {
      setList(res.data.data);
      setIsLoading(false);
    })
    .catch((error) => {
      setError(error.message);
      setIsLoadding(false);
     });
  }, [token]);


  if (isLoading) {
    return <h2>Loading...</h2>;
  }
  
  if (error) {
    return <h2>{error}</h2>;
  }

// 생략
};
import { useQuery } from "@tanstack/react-query"; 

const Todos = () => {
  const token = localStorage.getItem("token") || "";


  const Keys = {
    all: ["todos"] as const,
    details: () => [...Keys.all, "detail"] as const,
    detail: (id: string) => [...Keys.details(), id] as const,
  };

  const { isLoading, data, isError } = useQuery(
    Keys.all,
    () => ToDoAPI.getTodos(token).then((response) => response.data));

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

  if (isError) {
    return <h2>error occur</h2>;
  }

  return (
   // 생략
};




어려울줄 알았는데 여기까진 생각보다 간단했다
다음엔 추가 수정 삭제도 구현 ㄱ

profile
개발자로 성장하기 위한 아카이브 😎

0개의 댓글