[TIL] 내일배움캠프 React 과정 2022.12.22

Today I Learned

  • 리액트 팀 프로젝트 시작!
  • React-Query 강의 시청

 


React-Query

상태의 종류

  • 클라이언트 상태 : 뷰를 위한 데이터
    • 한 유저만을 위한 데이터 (세션 간 지속될 필요 없는 데이터)
    • 예시) input 입력 값을 state로 관리(렌더링에 반영하기 위한 데이터)
  • 서버 상태 : 서버에서 가지고 오는 데이터
    • 여러 유저가 공유해야하는 데이터 (세션 간 지속되어야 하는 데이터)
    • 예시) 게시물 리스트 (DB에 저장되어 있는 데이터)

 

React Query 상태 관리 흐름

  • fetching : 데이터 요청 중
  • fresh : 데이터를 갓 받아온 직후 / 컴포넌트의 상태가 변하더라도 데이터 재요청하지 않음
  • stale : 데이터 만료 / 최신화가 필요한 데이터
  • inactive : 쿼리가 언마운트 된 상태 (더는 사용하지 않는 상태) 쿼리가 언마운트된다고 해서 비동기 요청이 취소되는 것은 아니고 프라미스가 일단 만들어지고 언마운트된 거라면 데이터는 캐시에 살아 있을 수 있다. 
  • delete : 완전히 삭제된 상태 (캐시 데이터가 메모리에서 삭제됨)

 

QueryClientProvider 설정하기

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <QueryClientProvider client={queryClient}>
    {/* devtools */}
    <ReactQueryDevtools initialIsOpen={true} />
    <App />
  </QueryClientProvider>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

useQuery란?

const my_first_query = useQuery(쿼리 키, 쿼리 함수, 옵션);

useQuery는 어떤 데이터를 가져와서 캐싱하기 위해 사용하는데 쿼리 키, 쿼리 함수, 옵션으로 구성되어 있다.

  • 쿼리 키 : 문자열 / 배열 등을 넘길 수 있다. 이 키는 캐싱 처리하는데에 사용한다.
  • 쿼리 함수 : Promise를 리턴하는 어떤 함수를 넘기는데 api와 통신하는 함수가 주로 들어간다.
  • 옵션 : useQuery()를 위한 옵션들을 넣어준다.

 

my_first_query에는 아래같은 데이터가 담긴다.

  • data : 서버 요청에 대한 데이터
  • isLoading : 캐시가 없는 상태에서의 데이터 요청 중인 상태 (true / false)
  • isFetching : 캐시의 유무 상관없이 데이터 요청 중인 상태 (true / false)
  • isError : 서버 요청 실패에 대한 상태 (true / false)
  • error : 서버 요청 실패 (object)
  • refetch : 데이터를 새로 패칭하는 함수

 

useQuery로 넘길 수 있는 옵션

  • cacheTime : 언마운트된 후 어느 시점까지 메모리에 데이터를 저장하여 캐싱할 것인지를 결정 기본 값 : 30000(5분)
  • staleTime : 쿼리가 fresh 상태에서 stale 상태로 전환되는 시간 기본 값 : 0
  • refetchOnMount : 컴포넌트 마운트시 새로운 데이터 패칭 기본 값 : true (false일 경우엔 마운트해도 새로운 데이터를 가지고 오지 않아요.)
  • refetchOnWindowFocus : 브라우저 클릭 시 새로운 데이터 패칭 기본 값 : true (false일 경우엔 다른 탭으로 갔다가 돌아와도 데이터를 새로 가져오지 않아요.)
  • refetchInterval : 지정한 시간 간격만큼 데이터 패칭 / 브라우저 바깥에 있을 경우(다른 탭에 있는 등) 실행되지 않아요! 기본 값 : 0
  • refetchIntervalInBackground : 브라우저에 포커스가 없어도 refetchInterval에서 지정한 시간 간격만큼 데이터 패칭 기본 값 : false
  • enabled : 컴포넌트가 마운트 되어도 데이터 패칭 ❌ 기본 값 : true useQuery의 리턴 값 중 refetch가 있었죠! enabled가 true일 때는 refetch를 통해 데이터를 패칭해야 합니다.
  • onSuccess : 데이터 패칭 성공 시 콜백 함수
  • onError : 데이터 패칭 실패 시 콜백
  • initialData : 초기 데이터 쿼리 생성 전이나 아직 캐싱되지 않았을 경우, 이 데이터를 초기 데이터로 써요!
  • select : 데이터 패칭 성공 시 가져온 데이터를 변환해주고 싶을 때, 원하는 데이터 형식으로 변환하기 위한 콜백 (여기서 return으로 변환한 데이터를 반환해주면 useQuery의 결괏값인 data가 변해요.)

 

사용 예시

import { useQuery } from "react-query";
import axios from "axios";

// promise를 반환합니다!
// mock api에서 sleepList를 가져와요
const getSleepList = () => {
  return axios.get("http://localhost:5001/sleep_times");
};

function App() {
  const sleep_query = useQuery("sleep_list", getSleepList, {
    onSuccess: (data) => {
      console.log("성공했어!", data);
    },
  });

  console.log(sleep_query);
  console.log(sleep_query.data);

  return (
    <div className="App">
     <div>
        {sleep_query.data.data.map((d) => {
          return (
            <div>
              <p>{d.day}</p>
              <p>{d.sleep_time}</p>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default App;

 

 

useMutation이란?

const my_first_mutation = useMutation(뮤테이션 함수, 옵션);

useMutation은 어떤 데이터를 변경할 때(생성/수정/삭제) 사용한다. useQuery()와 같은 리턴 값 + mutate()를 리턴한다. 컴포넌트가 언마운트되면? → onSuccess, onError 같은 추가 콜백이 실행되지 않는다.

 

중요! onMutate: mutate 함수가 실행되기 전 실행하는 함수로, mutation 함수가 받을 변수가 전달된다.

  • mutate(useMutation의 콜백으로 넘겨줄 데이터, 옵션) : 뮤테이션을 실행해줄 함수

 

사용 예시

import { useMutation, useQuery } from "react-query";
import axios from "axios";

// promise를 반환합니다!
// mock api에서 sleepList를 가져와요
const getSleepList = () => {
  return axios.get("http://localhost:5001/sleep_times");
};

// 데이터 추가 api 요청을 반환해요!
const addSleepData = (data) => {
  return axios.post("http://localhost:5001/sleep_times", data);
};

function App() {

  // ref를 잡아줘요.
  const day_input = React.useRef();
  const time_input = React.useRef();

  const sleep_query = useQuery("sleep_list", getSleepList, {
    onSuccess: (data) => {
      console.log("성공했어!", data);
    },
  });
  
  const queryClient = useQueryClient():
  
// useMutation을 써줍시다. 
  const { mutate } = useMutation(addSleepData, {
  	onSuccess: () => {
    	queryClient.invalidateQueries.("sleep_list");
    }
  });

  if (sleep_query.isLoading) {
    return null;
  }


// 입력 인풋 두개와 버튼도 만들어봐요.
  return (
    <div className="App">
      <div>
        {sleep_query.data.data.map((d) => {
          return (
            <div>
              <p>{d.day}</p>
              <p>{d.sleep_time}</p>
            </div>
          );
        })}
      </div>
      <input ref={day_input} />
      <input ref={time_input} />
      <button
        onClick={() => {
          if (
            day_input.current.value === "" ||
            time_input.current.value === ""
          ) {
            return;
          }
          const data = {
            day: day_input.current.value,
            sleep_time: time_input.current.value,
          };

          mutate(data);
        }}
      >
        데이터 추가하기
      </button>
    </div>
  );
}

export default App;

 

 

시작부터 에러 발생?!

팀 프로젝트를 시작하면서 먼저 router 연결과 컴포넌트 구조를 다같이 작성한 다음에 이것을 push해서 팀원들이 clone하는 방식으로 프로젝트를 시작하려고 했었는데 시작부터 router pathname이 잘못됐다고 에러가 발생했었다. 이것 때문에 꽤나 시간을 쏟았는데 결국 import 경로 설정을 잘못했다는 사소한 이유로 시작된 일이었다. 오히려 이런 사소한 부분이 더 해결하기 힘든 거 같은 느낌이다.😥

 


회고

드디어 오늘부터 팀 프로젝트 시작이다! 아침 발제 후 놀랍게도 거의 여섯 시간 가까이 팀 회의를 진행했다ㅋㅋㅋ 이게 맞는 건가..?하는 생각도 들었지만 시간을 투자하며 브랜치를 상세하게 나누고 API 명세서도 UPL과 데이터 형태까지 정말 상세하게 정하고 시작해서 기초를 튼튼히 쌓은 기분이다. 그리고 역할 분담할 때도 약간 비효율적으로 느껴질 정도로(?)  본문, 댓글 CRUD의 역할을 쪼개서 모든 팀원들이 CRUD를 경험할 수 있게 신경썼다.

 

나는 욕심 내서 로그인과 회원가입 기능을 구현하겠다고 자원했는데 과연 할 수 있을지 모르겠다... 회원가입보다는 로그인이 더 어려울 거 같고 회원가입의 경우에는 각종 유효성 검증을 해야 할 거 같아서 걱정이다. 일단 오늘은 유효성 검증 전혀 없이 기초적인 회원가입 기능은 구현했다.

 

그리고 리액트 쿼리를 쓸까 리덕스 thunk를 쓸까 튜터님께도 조언을 구했었는데 리액트 쿼리를 쓰면 리덕스를 거의(혹은 아예?) 사용하지 않게 되기 때문에 일단은 리덕스와 전역 state에 대해 더 깊이 이해하기 위해서, 그리고 아직 리덕스를 완전히 이해하지 못한 팀원들을 위해서 리액트 쿼리보다는 thunk 사용하는 것을 추천해 주셨다. 오늘 처음으로 리액트 쿼리 강의를 시청했었는데 모듈 파일도 필요 없고 state 관리 안 해도 되는 게 정말 신세계처럼 느껴지긴 했다.😳 하지만 앞으로 리액트 쿼리를 써볼 기회는 많을 테니까 다음 단계로 넘어가기 전에 일단 리덕스와 state를 깊이 이해하는 것을 목표로 한다!