Ayden's journal

당신은 리액트 쿼리가 필요하지 않을 지도 모른다

이 포스트는 Dominik Dorfmeister가 자신의 블로그에 올린 You Might Not Need React Query 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는 사견이 포함되어있기도 하다.

 

리액트 서버 컴포넌트와 함께라면 리액트 쿼리를 쓸 필요가 없어지는 걸까요? 아마도 이 문제는 지난 몇 달간 제가 가장 많이 받은 질문이었을 겁니다. 그리고 이에 대한 제 대답은 "저도 잘 모르겠는데요?"입니다. 이 사실을 기억해주세요. 이 업계의 대부분의 개발자들이 그러하듯 저 역시도 필요한 게 생길 때마다 그때그때 하나씩 만들어왔다는 사실을 말이죠. 만약 여러분께서 저에게 뭔가 그럴듯한 계획이 있을 거라 기대하고 계셨다면, 죄송하게 되었습니다. 이 길이 어디서 끝날지는 저도 여러분 만큼이나 궁금하거든요.

 

사실대로 이야기하겠습니다. data fetching 분야 라이브러리의 메인테이너로써 저는 엄청난 공포를 느끼고 있습니다. 바로 서버 컴포넌트와 서스펜스 때문에요.

"이게 리액트 쿼리에 어떤 영향을 줄까요?" 좋은 질문입니다. 저는 답을 알아야 할 것 같지만 아직은 잘 모르겠습니다. 지금 엄청난 가면 증후군을 느끼고 있어요.

 

이전에 저는 위와 같이 이야기했었습니다. 하지만 이제 저는 이 문제를 조금 더 자세히 살펴볼 시간이 생겼고, 이 주제에 대해 저보다 더 정통한 몇몇 분들과도 이야기를 나누어보았습니다. 이제 저는 저의 생각을 공개적으로 밝힐 만큼 이 주제에 대해 충분히 확신을 가지고 있습니다. 그렇지만 이것은 어디까지나 제 생각에 불과합니다. 그러니 가볍게 들어주세요 :)

 

My Take : 제 생각은요

우리가 가진 모든 도구는 결국 우리가 가진 문제를 해결하는 데 도움이 되어야 합니다. 전통적으로 리액트는 여러분의 어플리케이션에서 어떻게 데이터를 가져오던지 별 신경을 쓰지 않았습니다. 그냥 여기 useEffect가 있으니 이거 가지고 님들이 알아서 하쇼, 같은 태도였죠.

이 시기에 리액트 쿼리나 swr과 같은 라이브러리가 태어났습니다. 이들은 만족스러운 개발자 경험을 제공하며 빠르게 채택되었고, 사용자를 위한 개선점을 제공하였으며, 이를 통해 메워야 할 간극을 메워주었습니다. 리액트 라우터도 비슷한 결이라 할 수 있겠습니다. "뷰(view)" 라이브러리가 기본적으로 아무것도 제공하지 않을 때 라우팅에 대한 니즈를 충족시켜주었죠.

서버 사이드 랜더링이 중요해지던 시절, 저희는 초기 페이지 로딩을 빠르게 제공하기 위해 서버 측에서 진행되는 html 사전 랜더링에 집중했습니다. 이후 어플리케이션은 완전한 SPA처럼 동작하며 클라이언트 사이드 페이지 전환 등을 수행하게 되죠. 이런 환경에서 리액트 쿼리 역시 중요한 역할을 맡습니다. 초기 data fetching을 서버에서 수행한 뒤, 이를 클라이언트에서 하이드레이션하는 방식으로 말이죠. 덕분에 우리는 서버를 통해 가능한 빠른 시점에 캐시를 채워넣을 수 있게 되었습니다.

 

So what changed? : 그래서 뭐가 바뀐 건데?

시대가 변하고 있으며, 상황은 좋아지고 있습니다. 그저 좌우로 흔들리는 진자 운동 같아보일지라도, 실제로는 앞으로 나아가고 있어요!

 

리액트는 여전히 컴포넌트를 랜더링하는 라이브러리일 뿐입니다. 그렇지만 서버 컴포넌트는 서버에서 사전에 랜더링이 가능한 새로운 어플리케이션 아키텍처를 제공합니다. 이러한 사전 랜더링은 런타임이나 빌드 타임 때 발생하게 되며, 클라이언트에서 쿼리해야 하는 API를 작성할 필요 없이 데이터에 접근할 수 있도록 합니다.

export default async function Page() {
  const data = await fetch(
    `https://api.github.com/repos/tanstack/react-query`
  )

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
    </div>
  )
}

리액트 컴포넌트 안에서 async/await가 그냥 된다는 사실이 놀랍고, 프레임워크가 문제를 파악하고 그에 대한 최고 수준의 솔루션을 제공하는 모습은 매우 흥미롭습니다. 이로 인해 아키텍쳐를 사용하고자 하는 어플리케이션들의 상황이 극적으로 달라졌습니다. 무엇보다 리액트 쿼리는 클라이언트에서의 비동기 상태를 관리하는 라이브러리입니다. 만약 data fetching이 서버에서만 ─ 아주 배타적으로 ─ 이루어진다면, 뭐하러 리액트 쿼리가 필요하겠어요?

 

You Might Not Need It : 안 필요할 수도 있겠지

그리고 그에 대한 제 대답은 "넌 리액트 쿼리가 필요하지 않을 수도 있다"는 겁니다. Next.js나 Remix와 같은 성숙한 프레임워크를 사용하여 새로운 어플리케이션을 시작하고자 한다면, 이미 data fetching과 mutation 관련 로직이 잘 구성되어있으니, 굳이 리액트 쿼리를 쓸 필요는 없을 겁니다.

제가 리액트 쿼리의 메인테이너이기는 하지만, 가능한 모든 상황에서 리액트 쿼리를 쓰라고 하지는 않습니다. 필요하다면 다른 도구를 사용해도 괜찮습니다. 앞서 말씀드렸던 것처럼, 만약 어떠한 도구를 쓰기로 결정했다면, 그 이유는 다른 것이 아니라 당면한 문제를 해결하기 위해서여야 하니까요.

 

Integration : 어떻게 결합할 것인가

서버 컴포넌트라는 새로운 세상에는 아직 리액트 쿼리가 필요한 지점이 많이 남아있습니다. 우선, 대부분의 프로젝트는 바닥부터 시작하는 게 아닐겁니다. 이미 많은 어플리케이션이 수년간에 걸쳐 개발되어왔고, 이들은 점진적으로 app router를 도입하겠지만, 서버 컴포넌트를 도입하기 위해서는 일종의 재구성 작업(re-architecture)이 필요합니다.

재구성 기간 동안 리액트 쿼리는 app router 및 서버 컴포넌트와 아주 잘 결합됩니다. 여러분은 일부 컴포넌트를 서버에서만 fetch 하도록 수정하거나, 서버 컴포넌트를 사용해 캐시를 prefetch하고 이를 useQuery를 사용하는 클라이언트 컴포넌트로 전달할 수도 있죠. 리액트 쿼리 공식 문서에는 이러한 결합에 대한 좋은 가이드가 제공되고 있으며, 이를 바탕으로 주의해야 할 부분에 대한 게시글을 추가로 작성할 계획입니다.

 

Hybrid approach : 하이브리드 접근방식

서버 컴포넌트는 아직 충분히 성숙하지 않았고, 몇몇 기능에 대해서는 특히 더 그렇습니다. 하이브리드 접근방식을 사용하면 이러한 상황에서 특히 유용합니다.

예를 들어볼까요. 무한 스크롤 기능을 구현하고자 할 때 첫 페이지는 서버에서 prefetch하고, 사용자가 끝까지 스크롤하면 클라이언트에서 이후 페이지를 가져오는 경우가 있겠네요. 또는 어플리케이션이 네트워크 연결 없이도 작동해야 한다는 요구 사항이 있을 수도 있죠. 어쩌면 명시적인 사용자 상호작용 없이도 항상 최신 데이터를 제공하는 ─ 주기적으로 데이터를 가져오거나, 리액트 쿼리가 제공하는 스마트한 auto-refetches 기능을 생각해보세요 ─ 사용자 경험이 만족스러웠을 수 있습니다.

리액트 쿼리는 이러한 경우들에 대해 최선의 해결책을 가지고 있습니다. 따라서 서버 컴포넌트와 리액트 쿼리를 함께 사용하는 것이 괜찮을 때가 분명 존재합니다. 하지만 데이터를 가져다가 사용자에게 제공할 목적으로만 리액트 쿼리를 사용해왔다면, 제 생각에는 서버 컴포넌트를 사용하는 편이 더 나을 겁니다. 그리고 서버 액션이 쓸만한 패턴으로 자리잡는다면, 아마 데이터를 업데이트할 때도 리액트 쿼리가 필요 없어질 겁니다.

 

It's not a "killer" : 킬러라고 할 것 까지는 아니고

여러 이유로 인해 모두가 서버 컴포넌트를 채택하지는 않을 거라 생각합니다. 백엔드가 NodeJs로 작성되지 않았을 수도 있고, 프론트엔드가 전용 서버 없는 SPA일 지도 모르죠. 어쩌면 React Native를 사용하여 모바일 어플리케이션을 만들고 있을 수도, 리액트가 아닌 곳에서 TanStack Query를 쓰는 것일 수도 있습니다.

(번역자 주 : Dominik Dorfmeister 포스트 내내 리액트 쿼리라고 하였고, 제목에서조차 React Query라고 적었다. 하지만 22년 12월을 기점으로 하여 리액트 쿼리는 이름을 TanStack Query로 변경하였다. 이는 리액트에 국한되지 않고 Vue, Svelte, SolidJs 등 다양한 플랫폼에서 사용할 수 있도록 하는 프로젝트의 방향성을 반영하기 위함이었다.)

 

data fetching 이외의 분야에서 리액트 쿼리를 사용할 수도 있습니다. 아래의 트윗에 달린 리트윗을 통해 영감을 얻을 수 있을지도요.

 

이 모든 경우에서 리액트 쿼리는 클라이언트 비동기 상태 관리자로 사용하기 적합합니다. 하지만 이를 기본적으로 지원하는 프레임워크를 사용하기로 했다면, 제발 그걸 쓰세요! Remix를 쓰기로 했으면서 loader에서 데이터를 fetch하지 않을 이유가 어디 있겠어요? 🤷‍♂️

저는 Tanstack Query를 리액트 서버 컴포넌트 외부에서, 심지어는 리액트 서버 컴포넌트와 결합하여 사용하는 경우가 여전히 많을 것이라 예상하고 있습니다. 다들 서버 컴포넌트 이야기만 하고 있지만, 어쩔 수 없죠. 모두가 한 번쯤 써보고 싶어하는 기술이잖아요.

하지만 리액트 서버 컴포넌트는 이제 갓 출시된 ─ 아직 초기 단계에 불과한 ─  기술입니다. 이를 제대로 사용하기 위해서는 프레임워크, 라우터, 번들러가 서로 긴밀하게 결합되어야 하죠. 추가적인 부하를 처리해낼 수 있는 서버 인프라도 필요할 겁니다. 반복해서 말하는 듯하지만 결국 이런 거죠.

 

세상에 공짜는 없다. 모든 것은 등가교환이다.

인생은 실전이야…?

 

지금 당장 모든 것을 서버 컴포넌트로 옮길 필요는 없다고 생각합니다. 저도 Next.js 사용자이기에, 우리의 어플리케이션을 app router로 옮기는 것을 기대하고 있습니다. 특히 nested routes로부터 생기는 이점을 누리고 싶거든요. 그리고 좀 더 정적인 data fetching을 사용하는 ─ 가령 staleTime: Infinity를 설정해도 문제가 없는 ─ 경우에는 서버 컴포넌트로 옮길 예정입니다.

 

하지만 리액트 쿼리에 대한 사망 보도는 크게 과장된 것입니다.

블로그의 정보

Ayden's journal

Beard Weard Ayden

활동하기