20230221 [react] - 커스텀 훅,react query

lionloopy·2023년 2월 21일
0

리액트

목록 보기
16/18

커스텀 훅

  • 반복되는 로직이나 기능을 우리만의 훅으로 만든다.
    :같은 기능을 하는 handler들이 많이 있다면, 하나로 묶어도 되니까!

기존 코드

import React, { useState } from "react";

function App() {
 const [title, setTitle] = useState("");
 const [comment, setComment] = useState("");

 const onTitleHandler = (event) => {
   setTitle(event.target.value);
 };
 const onCommentHandler = (event) => {
   setComment(event.target.value);
 };

 return (
   <div>
     <input value={title} onChange={onTitleHandler} />
     <input value={comment} onChange={onCommentHandler} />
   </div>
 );
}

export default App;

커스텀 훅
hooks/useInput.js

import { useState } from "react";

const useInput = () => {
  const [value, setValue] = useState("");
  const handler = (event) => {
    setValue(event.target.value);
  };
  return [value, handler];
};

export default useInput;

App.js

import React, { useState } from "react";
import useInput from "./hooks/useInput";

function App() {
  const [title, onTitleHandler] = useInput("");
  const [comment, onCommentHandler] = useInput("");

  return (
    <div>
      <input value={title} onChange={onTitleHandler} />
      <input value={comment} onChange={onCommentHandler} />
    </div>
  );
}

export default App;

React query

기존 미들웨어의 한계

  • 보일러 플레이트: 코드량이 너무 많다.(extra reducer등)
  • 규격화 문제: 리덕스가 비동기 데이터 관리를 위한 전문 라이브러리는 아니었다.

리액트 쿼리의 장점

  • 너무 쉽고, 책임에서 자유롭다.
  • 내가 만든 부분이 아니기 때문에 에러가 일어나도 내 잘못이 아니다.
  • 기존 thunk보다 쉽고 직관적이다.

리액트 쿼리의 키워드

  • query: 어떤 데이터에 대한 요청(axios의 경우 get)
  • mutation: 어떤 데이터를 변경,추가,수정,삭제(axios의 경우 post)
  • query invalidation:위에서 보았던 query를 무효화 시키는 것
    기존에 가져온 query는 서버 데이터이기 때문에 언제든지 변경이 있을 수 있다. 그런 경우 기존의 쿼리를 무효화 시킨 후 최신화 시켜야 한다. 이런 과정을 리액트 query는 query invalidation으로 알아서 진행해준다.

기본 셋팅
App.js

import React from "react";
import { QueryClient, QueryClientProvider } from "react-query";
import Router from "./shared/Router";

const queryClient = new QueryClient();

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <Router />
    </QueryClientProvider>
  );
};

export default App;

api/todos.js

import axios from "axios";

const getTodos = async () => {
  const response = await axios.get(`http://localhost:4000/todos`);
  return response.data;
};
const addTodo = async (newTodo) => {
  await axios.post(`http://localhost:4000/todos`, newTodo);
};

export { getTodos, addTodo };

components/Todolist.js

import React from "react";
import Todo from "../Todo";
import { getTodos } from "../../../api/todos";
import { useQuery } from "react-query";

function TodoList({ isActive }) {
  // const todos = useSelector((state) => state.todos);
  const { isLoading, isError, data } = useQuery("todos", getTodos);
  if (isLoading) {
    return <div>로딩중입니다..</div>;
  }
  if (isError) {
    return <div>에러입니다..</div>;
  }

  return (
    <StyledDiv>
      <StyledTodoListHeader>
        {isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
      </StyledTodoListHeader>
      <StyledTodoListBox>
        {data
          .filter((item) => item.isDone === !isActive)
          .map((item) => {
            return <Todo key={item.id} todo={item} isActive={isActive} />;
          })}
      </StyledTodoListBox>
    </StyledDiv>
  );
}

export default TodoList;

components/Input.js

import { addTodo } from "../../../api/todos";
import { useMutation, useQueryClient } from "react-query";

function Input() {
  const dispatch = useDispatch();

  const queryClient = useQueryClient();
  const mutation = useMutation(addTodo, {
    onSuccess: () => {
      queryClient.invalidateQueries("todos");
    },
  });
  const newTodo = {
   id:id,
   title:title,
   content:content
   ]
  // dispatch(addTodo(newTodo));
  mutation.mutate(newTodo);

1.App.js에서 new QueryClient를 만들고, Provider처럼 감싸준다.
2.api폴더를 만들고 그 안에서 axio를 활용한다.
3.get방식으로 불러오는 getTodos와 post방식으로 newTodo값을 입력할 addTodo를 만들고 export로 내보낸다.
4.이제 이 방식으로 useSelector를 활용하여 데이터를 불러올 필요가 없어진다. 필요한 곳에서(todolist.js)데이터를 불러오기 위해 useQuery를 사용한다.
5.이때 내가 정할 name 키워드('todos')를 꼭 넣어주고, 만든 getTodos를 인자로 같이 넣어준다.
6.리액트 쿼리는 isLoading, isError, data를 기본적으로 탑재하고 있으므로, if문을 써서 상황을 나눠준다.
7.밑에 return문에도 이제 useSelector로 데이터를 불러오지 않고, 알아서 data에 저장되었기 때문에 data로 map을 돌릴 수 있다.
8.input값을 입력하는 곳에서도(Input.js) useQueryClient를 선언해주고,
9.입력값을 받아 입력하기 위해 mutation을 활용한다. 만들어온 addTodo를 불러와 넣어주고, onSuccess를 넣어 성공했을 시 함수를 실행시킨다.
10.queryClient를 무효화시키고, 최신화시키기 위해 만들어놓은 useQuery의 키워드 값을 가져온다(쿼리키 'todos')
=> input값에 입력을 하고 확인을 누르는 순간, mutation.mutate가 동작이 되면서(post)데이터는 입력이 된다. onSuccess가 발동이 되고, queryClient에 있는 invalidateQueries가 발동이 되면서, 우리가 원래 읽어왔었던 todos는 맞지 않아(최신이 아니야!) invalidation할거야 하면서 다시 실행해주겠다는 로직이다.

useQuery

  • 첫번째 인자 'todos': 이것을 쿼리의 키라고 부른다.
    :refetchinig(invalidation)할 때 쓰인다.(실시간 데이터 최신화)
    :쿼리키는 unique해야한다. 단어 한 개로 이루어져 있어야 한다.
    :쿼리키는 배열 형태로도 쓸 수 있다.
  • 두번째 인자로는 비동기 함수가 들어간다. 이것을 쿼리 함수라고 한다.
    :쿼리 함수는 promise객체를 return한다.
    :오류 처리 관련 로직을 삽입해서 처리해줘야 한다.
    :(try~catch 등)
  • useQuery를 통해 얻은 결과물은 객체이다.(isLoading, isError, data)
    :조회를 시작하는 순간 isLoading은 true가 된다.
    :조회 결과 오류가 나면 isLoading 은 false가 되고, isError는 true가 된다.
    :조회 결과가 정상이 되면 isSuccess가 true가 되고, isLoading은 false가 된다. data객체를 통해 상세하게 결과를 조회할 수 있다.
  • mutation은 query와 다르게 CUD에서 사용된다.
    :mutation.mutate(인자)의 인자는 반드시 한 개의 변수 또는 객체여야 한다.
    :결과를 객체 형태로 가지고 있다.
profile
Developer ʕ ·ᴥ·ʔ ʕ·ᴥ· ʔ

0개의 댓글