Frontend

react-query를 도입해보자

mechaniccoder 2022. 8. 28. 14:45

안녕하세요. 이번 포스팅에서는 지금 다니고 있는 회사에서 react-query를 왜 도입했고 어떻게 사용하고 있는지에 대해 공유해볼까 합니다.

도입한 이유

기존 프로젝트에서는 상태관리를 하기 위해 redux-toolkit을 사용했습니다. 서버 데이터를 불러오기 위해 내장된 redux-thunk를 사용했는데요. 비동기 상태에 대한 코드를 아무래도 직접 관리해줘야 하니, 앱의 규모가 커지면 커질수록 작성해야 하는 코드가 늘어나는 불편함이 있었습니다.
이를 해결하기 위해 react-query를 도입하게 됐습니다.

적용 방법

react-query를 잘 사용하기 위해 했던 고민들 중에 몇가지를 공유해보겠습니다.

  • custom hook
  • selector

Custom Hook

react-query에서 제공해주는 기본적인 API를 컴포넌트에서 직접 사용하지 않고 custom hook으로 추상화해서 사용했습니다. 이렇게 사용하면 구현을 숨길 수 있으며, 도메인에 대한 문맥을 제공해줄 수 있습니다.

const useTodosQuery = () => {
  return useQuery(todosQueryKey.list, fetchTodos)
}
​
const useCreateTodoMutation = () => {
  return useMutation((newTodo) => createTodo(newTodo))
}
const SomeComponent = () => {
  const todos = useTodosQuery()
  const createTodo = useTodoCreateMutation()
  
  const handleClick = () => {
    createTodo.mutate(todo)
  }
  
  if (todos.isLoading) {
    return <div>loading...</div>
  }
  
  if (todos.error) {
    return <div>error...</div>
  }
  
  return todos.map(todo => {
    //...
  })
}

아 그리고 destructuring을 하지 않은 것에 대해 의아할 수도 있을 것 같은데요. 처음 react-query를 사용할 때는 destructuring을 해서 이름을 다시 붙여주는 방식으로 사용했었는데요. 다수의 query custom hook을 사용할 때 변수가 충돌되지 않도록 매번 이름을 다시 붙여주는 번거로움이 있었습니다.

const SomeComponent = () => {
  const { data: todos, isLoading: isTodosLoading, isError: isTodosError} = useTodosQuery()
  const { data: user, isLoading: isUserLoading, isError: isUserError} = useUserQuery()
  const { data: billing, isLoading: isBillingLoading, isError: isBillingError} = useBillingQuery()
}

그래서 destructuring을 하지 않고 사용하는 것으로 리팩토링을 했습니다.

const SomeComponent = () => {
  const todos = useTodosQuery()
  const user = useUserQuery()
  const billing = useBillingQuery()
}

Selector

redux에서는 selector 함수와 useSelector를 사용해 원하는 데이터를 가져올 수 있죠. react-query에서는 서버 데이터를 cache에 저장하기 때문에 캐시된 데이터를 쉽게 꺼내올 수 있는 인터페이스가 필요했습니다.
이를 위해 useQuery에서 옵션으로 제공해주는 select 를 활용했습니다. redux에서 사용하는 방식처럼 원하는 데이터를 추출해주는 selector함수를 query custom hook의 인자로 넘겨서 사용하게끔 설계했습니다.

const useTodosQuery = <T extends (todos: Todo[]) => any>(selector?: T) => {
  return useQuery(todosQueryKey.list, fetchTodos, {
    select: todos => selector?.(todos) ?? fetchTodosDto
  })
})
type Todo = {
  text: string;
  done: boolean;
}
​
const selectIsTodosAllDone = (todos: Todo[]) => todos.every(todo => todo.done)
​
const SomeComponent = () => {
  const isTodosAllDone = useTodosQuery(selectIsTodosAllDone)
}

마치며

react-query는 실무에 적용하기 전부터 관심있게 지켜봤던 기술입니다. 다른 회사의 컨퍼런스 영상을 보면 react-query를 도입해서 비동기 처리를 하는 코드를 간소하게 리팩토링을 했다는 얘기를 많이 들었는데요. 실제로 사용해보니 확실히 비동기 처리를 간편하게 만들어주는 라이브러리인 것 같습니다.
사실 react-query를 도입하는 과정이 쉽지는 않았습니다. "아직 학습이 된 상태가 아니기 때문에 좀 부담스럽다", "새로운 기술을 도입하면서 기한 내에 프로젝트를 끝낼 수 없을 것 같다"는 부정적인 의견도 있어 팀원들의 공감이 필요했기 때문입니다. 하지만 장기적으로 봤을때 도움이 될 거라는 것에 대한 확신이 있었고 적극적으로 의견을 주장했습니다.
기술 도입에 대한 설득, 실제로 도입해서 이를 성공적으로 이끄는 경험을 처음해보았는데요. 프로젝트 회고때 많은 팀원들이 react-query를 도입한 것에 대해 만족한다는 얘기를 해줬을때 정말 뿌듯했습니다.
기술 도입을 할때 중요한 것은 기술이 해결하는 문제가 무엇인지 아는 것도 중요하지만, 팀원들의 공감이 선행되어야 한다는 것을 배웠던 경험이었습니다.