React-Query 프로젝트 적용기

윤다은·2022년 12월 7일
2
post-thumbnail

리액트 쿼리를 적용한 이유

1. 동료 간의 러닝커브를 최소화

처한 환경마다 다르겠지만, 이미 다른 서비스에서 리액트 쿼리를 활용하고 있었기 때문에 러닝 커브를 최소화하기 위해 리액트 쿼리를 적용했습니다

2. 프로젝트 내에서 업데이트하는 항목이 많다.

한 사람이 각자의 개별 데이터를 다루는 것이 아니라 여러 사람이 동일한 데이터를 업데이트 할 수 있었기 때문에 경우에 따라 캐싱이나 데이터 최신화가 필요했습니다.
여기에 더해 로딩과 오류 처리를 일일이 하는 것이 까다로운 작업이었고, 이를 간단히 해 줄 라이브러리가 필요했습니다.

3. 컴포넌트 간의 의존성을 낮추기 위해

리액트 쿼리를 적용하기 전엔 서버에서 모든 데이터를 가져와서 전부 props로 내려주는 형태였습니다. 하지만 이렇게 하면 새로운 컴포넌트가 중간에 들어가거나 삭제되는 경우에 props를 다 drilling 해야하기 때문에 개발에 피로도가 느껴졌습니다.
그래서 이를 필요한 데이터에 따라 훅으로 나누고자 했고 react-query가 적합하다고 생각했습니다.

프로젝트에 적용하기에 앞서

라이브러리의 자세한 사용 방법은 공식 문서에 잘 나와있기 때문에 따로 기입하지 않겠습니다.
만약 이미 있는 프로젝트의 라이브러리에 리액트 쿼리를 적용해야 할 경우 프로젝트 안에서 구조를 어떻게 나누어야 하는 지에 집중해서 작성합니다.

리액트 쿼리 그냥 사용하면 될까?

리액트 쿼리에선 useQuery라는 함수가 있습니다

const {data} = useQuery(queryKey, queryFn)

첫번째 인수는 queryKey는 캐싱을 위해 사용하는 키 값입니다. 두번째 인수는 실제 데이터를 패칭하는 함수를 넣습니다.
실제 프로젝트에선 그대로 사용하면 어떨까요?

import getUser from './api'

function Component(){
  ...
  const {data} = useQuery('user', getUser)
  ...
  return <div></div>
}

별 차이가 없어보일 수 있습니다. 하지만 더 깔끔해질 방법이 있을 것 같습니다.
만약 이 컴포넌트 안에서 여러 정보를 불러와야 하는 경우는 어떨까요?

import getUser from './api'
import getCustomSetting from './api'

function Component(){
  ...
  const {data} = useQuery('user', getUser)
  const {data} = useQuery('custom Setting', getCustomSetting)
  ...
  return <div></div>
}

한 컴포넌트가 아니고 여러 컴포넌트에서 위와 같이 사용할 경우 아래와 같은 단점이 생길 수 있습니다.
1. 쿼리 키가 중복되거나 오용될 수 있습니다. 만약 똑같은 api 함수에 다른 작업자가 모르고 다른 쿼리 키를 사용한 경우 원하던 동작이 이루어지지 않을 수 있습니다.
2. 코드를 봤을 때 useQuery가 제일 눈에 띄는데 막상 의미가 있는 부분은 그 뒷부분이 더 큰 것 같습니다.

위 문제들을 개선하면 좀 더 가독성 있는 코드가 될 것 같습니다.

리액트 쿼리도 체계적으로 쓰자

1. 쿼리 키 관리

쿼리 키를 관리하는 데에도 여러 가지 방법이 있습니다. 앞으로 나올 내용들은 실제 프로젝트에 쓰인 방식은 아니었지만, 작성하는 시점에 더 좋은 방법으로 적습니다.

모든 쿼리는 배열로 관리합니다. 물론 리액트 쿼리에선 모두 문자열로 바꾸어 쓰긴 하지만, 문자열로 쓰면 쿼리키에 넣는 내용들을 문자열로 바꿔주기 번거롭다는 생각이 듭니다.

queryKeys.js 파일을 생성하고 아래와 같이 만들어볼 수 있을 것 같습니다.

export const userKey = () => ['user']
export const customSettingKey = () => ['customSetting']
export const userProfileKey = (id) => ['user', 'profile', id]

쿼리 키를 한 파일에서 관리한다면 이름이 겹칠 일이 없을 겁니다. 만약 리스트의 일부 항목이나 여러 데이터 중 하나를 가져오는 경우 userProfile과 같이 id를 인수로 받아 활용하는 것도 좋습니다.

2. 커스텀 훅 만들기

위에 useQuery대신 useUser또는 useCustomSetting과 같은 이름으로 쓰인다면 훨씬 함수의 의미를 파악하기 쉬울 것입니다. 또한 API 함수를 컴포넌트에서 부르지 않아도 되니 컴포넌트 파일이 더 깔끔해질 것을 기대할 수 있습니다.

커스텀 훅을 만든다면 src/hooks하단에 모아두면 좋을 것 같습니다. 예를 들어 사용자의 정보를 가져오는 훅을 작성합니다

import { userKey } from 'src/queryKey'
import getUser from 'src/api'

const useUser = () => {
  const result = useQuery(userKey(), getUser)
  return result
}

export default useUser

타입스크립트를 사용한다면 useQuery의 인수에 맞는 타입을 인수에 선언합니다.

커스텀 훅을 작성하고 컴포넌트 파일에 커스텀 훅을 적용해봅니다.

import useUser from 'src/hooks/useUser'
import useCustomSetting from 'src/hooks/useCustomSetting'

function Component(){
  ...
  const {data} = useUser()
  const {data} = useCustomSetting()
  ...
  return <div></div>
}

아까보다 코드가 간결하고 컴포넌트 렌더링과 관련이 없는 정보들을 숨길 수 있습니다.

리액트 쿼리를 적용하면서

보통 라이브러리들에 대한 설명이나 사용 방법, 장∙단점에 대해선 잘 나와있지만, 이를 프로젝트에 어떻게 정리할 것인가에 대한 글이 없는 것 같아 작성하였습니다.
라이브러리를 적용할 때는 여러 곳에서 사용할 경우도 함께 고려하면 좋을 것 같습니다.

profile
코끼리가 코로 걸어다니는 코드를 지양합니다.

0개의 댓글