본문으로 건너뛰기

개요

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는 서버 측 렌더링과 매우 잘 작동합니다
  • 낙관적 업데이트: 낙관적 업데이트로 캐시를 손쉽게 업데이트합니다
  • 그 외에도 훨씬 더 많습니다...