본문으로 건너뛰기

개요

Solid Query는 웹 애플리케이션에서 서버 상태를 가져오고, 캐시하고, 동기화하고, 업데이트하는 작업을 간편하게 해주는 TanStack Query의 공식 SolidJS 어댑터입니다.

도입 배경

SolidJS는 사용자 인터페이스를 구축하기 위한 빠르고 반응형이며 선언적인 라이브러리로 인기를 얻고 있습니다. 기본적으로 많은 기능을 갖추고 있습니다. createSignal, createStore 같은 프리미티브는 클라이언트 상태를 관리하는 데 매우 적합합니다. 또한 다른 UI 라이브러리와 달리 SolidJS는 비동기 데이터 관리 방식에 대해 명확한 관점을 가지고 있습니다. createResource API는 SolidJS 앱에서 서버 상태를 처리하는 데 매우 적합한 프리미티브입니다. resource는 데이터가 로딩 상태일 때 Suspense 경계를 트리거하는 데 사용할 수 있는 특별한 종류의 signal입니다.

import { createResource, ErrorBoundary, Suspense } from 'solid-js'
import { render } from 'solid-js/web'

function App() {
const [repository] = createResource(async () => {
const result = await fetch('https://api.github.com/repos/TanStack/query')
if (!result.ok) throw new Error('Failed to fetch data')
return result.json()
})

return (
<div>
<div>Static Content</div>
{/* An error while fetching will be caught by the ErrorBoundary */}
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
{/* Suspense will trigger a loading state while the data is being fetched */}
<Suspense fallback={<div>Loading...</div>}>
<div>{repository()?.updated_at}</div>
</Suspense>
</ErrorBoundary>
</div>
)
}

const root = document.getElementById('root')

render(() => <App />, root!)

정말 놀랍습니다! 몇 줄의 코드만으로 API에서 데이터를 가져오고 로딩 및 오류 상태를 처리할 수 있습니다. 하지만 애플리케이션의 복잡성이 커지면 서버 상태를 효과적으로 관리하기 위해 더 많은 기능이 필요합니다. 이는 서버 상태가 클라이언트 상태와 완전히 다르기 때문입니다. 우선 서버 상태는 다음과 같습니다:

  • 사용자가 제어하거나 소유하지 않는 원격 위치에 영구 저장됩니다
  • 가져오기 및 업데이트를 위해 비동기 API가 필요합니다
  • 공동 소유권을 의미하며 본인 모르게 다른 사람이 변경할 수 있습니다
  • 주의하지 않으면 애플리케이션에서 잠재적으로 "최신 상태가 아니게" 될 수 있습니다

애플리케이션에서 서버 상태의 특성을 이해하고 나면 진행하면서 훨씬 더 많은 과제가 발생합니다. 예를 들면 다음과 같습니다:

  • 캐싱... (프로그래밍에서 아마도 가장 어려운 작업)
  • 동일한 데이터에 대한 여러 요청을 하나의 요청으로 중복 제거하기
  • 백그라운드에서 "오래된" 데이터 업데이트하기
  • 데이터가 "최신 상태가 아닌" 시점 파악하기
  • 데이터 업데이트를 최대한 빠르게 반영합니다
  • 페이지네이션 및 데이터 지연 로딩과 같은 성능 최적화
  • 서버 상태의 메모리 및 가비지 컬렉션 관리
  • 구조적 공유를 사용하여 쿼리 결과 메모이제이션하기

바로 이 지점에서 Solid Query가 필요합니다. 이 라이브러리는 createResource를 감싸고 서버 상태를 효과적으로 관리하기 위한 훅과 유틸리티 모음을 제공합니다. 별도 설정 없이 즉시 매우 훌륭하게 작동하며, 애플리케이션이 성장함에 따라 원하는 대로 맞춤 설정할 수 있습니다.

더 기술적인 측면에서 Solid Query는 다음과 같이 동작할 가능성이 높습니다:

  • 애플리케이션에서 복잡하고 잘못 이해되기 쉬운 수많은 코드 줄을 제거하고, 단 몇 줄의 Solid Query 로직으로 대체할 수 있도록 도와줍니다.
  • 새로운 서버 상태 데이터 소스를 연결할 걱정 없이 애플리케이션의 유지보수성을 높이고 새로운 기능을 더 쉽게 구축할 수 있게 합니다
  • 애플리케이션이 이전보다 더 빠르고 반응성이 뛰어나게 느껴지도록 하여 최종 사용자에게 직접적인 영향을 미치세요.
  • 대역폭을 절약하고 메모리 성능을 높이는 데 도움이 될 수 있습니다

설명은 충분하니, 이제 코드를 보여주세요!

아래 예시에서는 TanStack Query GitHub 프로젝트 자체의 GitHub 통계를 가져오기 위해 가장 기본적이고 단순한 형태로 Solid Query를 사용하는 모습을 확인할 수 있습니다:

import { ErrorBoundary, Suspense } from 'solid-js'
import {
useQuery,
QueryClient,
QueryClientProvider,
} from '@tanstack/solid-query'

function App() {
const repositoryQuery = useQuery(() => ({
queryKey: ['TanStack Query'],
queryFn: async () => {
const result = await fetch('https://api.github.com/repos/TanStack/query')
if (!result.ok) throw new Error('Failed to fetch data')
return result.json()
},
staleTime: 1000 * 60 * 5, // 5 minutes
throwOnError: true, // Throw an error if the query fails
}))

return (
<div>
<div>Static Content</div>
{/* An error while fetching will be caught by the ErrorBoundary */}
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
{/* Suspense will trigger a loading state while the data is being fetched */}
<Suspense fallback={<div>Loading...</div>}>
{/*
The `data` property on a query is a SolidJS resource
so it will work with Suspense and transitions out of the box!
*/}
<div>{repositoryQuery.data?.updated_at}</div>
</Suspense>
</ErrorBoundary>
</div>
)
}

const root = document.getElementById('root')
const client = new QueryClient()

render(
() => (
<QueryClientProvider client={client}>
<App />
</QueryClientProvider>
),
root!,
)

그런데 같은 작업을 하는 데 코드 줄이 더 많아 보이지 않나요?

네, 맞습니다! 하지만 이 몇 줄의 코드로 완전히 새로운 가능성의 세계가 열립니다. 위 예시에서는 쿼리가 5분 동안 캐시되므로, 5분 이내에 앱 어디에서든 같은 쿼리를 사용하는 새 컴포넌트가 마운트되면 데이터를 다시 가져오지 않고 캐시된 데이터를 사용합니다. 이는 Solid Query가 기본 제공하는 수많은 기능 중 하나일 뿐입니다. 그 밖의 기능은 다음과 같습니다:

  • 자동 다시 가져오기: 쿼리는 "stale 상태"(staleTime 옵션에 따라 최신 상태가 아님)가 되면 백그라운드에서 자동으로 다시 가져옵니다
  • 자동 캐싱: 쿼리는 기본적으로 캐시되며 애플리케이션 전체에서 공유됩니다
  • 요청 중복 제거: 여러 컴포넌트가 동일한 쿼리를 공유하고 하나의 요청을 보낼 수 있습니다
  • 자동 가비지 컬렉션: 더 이상 필요하지 않은 쿼리는 가비지 컬렉션됩니다
  • 창 포커스 시 다시 가져오기: 애플리케이션이 다시 포커스되면 쿼리를 자동으로 다시 가져옵니다
  • 페이지네이션: 페이지네이션 기본 지원
  • 요청 취소: 오래되었거나 원하지 않는 요청을 자동으로 취소합니다
  • 폴링/실시간: 간단한 refetchInterval 옵션으로 쿼리에 폴링 또는 실시간 업데이트를 쉽게 추가할 수 있습니다
  • SSR 지원: Solid Query는 서버 측 렌더링과 매우 잘 작동합니다
  • 낙관적 업데이트: 낙관적 업데이트로 캐시를 손쉽게 업데이트합니다
  • 그 외에도 훨씬 더 많습니다...