URQL
urql은 GraphQL 쿼리, 캐싱, 상태 관리를 위한 툴킷을 제공합니다.
개요 문서에 따르면:
urql은 GraphQL 초보자도 쉽게 사용할 수 있으면서도 확장 가능한 고도로 커스터마이징 가능한 GraphQL 클라이언트입니다. 앱이 성장함에 따라 정규화된 캐싱과 같은 기능을 추가할 수 있습니다. 동적 단일 앱 애플리케이션과 고도로 커스터마이징된 GraphQL 인프라를 지원하도록 설계되었습니다. 요약하면, urql은 사용성과 적응성을 우선시합니다.
jotai-urql은 URQL을 위한 Jotai 확장 라이브러리입니다. 이 라이브러리는 URQL의 모든 GraphQL 기능을 통합한 일관된 인터페이스를 제공하며, 기존 Jotai 상태와 함께 이러한 기능을 활용할 수 있게 해줍니다.
설치
이 확장 기능을 사용하려면 jotai-urql
, @urql/core
, 그리고 wonka
를 설치해야 합니다.
npm install jotai-urql @urql/core wonka
내보낸 함수들
atomWithQuery
: client.query를 위한 함수atomWithMutation
: client.mutation를 위한 함수atomWithSubscription
: client.subscription를 위한 함수
Basic usage
쿼리:
import { useAtom } from 'jotai'const countQueryAtom = atomWithQuery<{ count: number }>({query: 'query Count { count }',getClient: () => client, // 이 옵션은 전역적으로 `useRehydrateAtom([[clientAtom, client]])`를 사용하는 경우 선택 사항입니다.})const Counter = () => {// 첫 번째 작업 결과가 해결될 때까지 일시 중단됩니다. 오류, 부분 데이터 또는 데이터와 함께 해결됩니다.const [operationResult, reexecute] = useAtom(countQueryAtom)if (operationResult.error) {// 이 오류는 상위 ErrorBoundary에서 처리되어야 합니다.throw operationResult.error}// 여기서는 옵셔널 체이닝을 사용해야 합니다. 데이터가 이 시점에서 undefined일 수 있기 때문입니다(오류가 발생한 경우에만).return <>{operationResult.data?.count}</>}
Mutation:
import { useAtom } from 'jotai'const incrementMutationAtom = atomWithMutation<{ increment: number }>({query: 'mutation Increment { increment }',})const Counter = () => {const [operationResult, executeMutation] = useAtom(incrementMutationAtom)return (<div><buttononClick={() =>executeMutation().then((it) => console.log(it.data?.increment))}>Increment</button><div>{operationResult.data?.increment}</div></div>)}
함수에 전달되는 옵션의 간소화된 타입
type AtomWithQueryOptions<Data = unknown,Variables extends AnyVariables = AnyVariables,> = {// 문자열 쿼리, 타입이 지정된 문서 노드, 문서 노드 등을 지원합니다.query: DocumentInput<Data, Variables>// 제네릭/타입이 지정된 문서 노드 타입에 따라 동적으로 적용됩니다.getVariables?: (get: Getter) => VariablesgetContext?: (get: Getter) => Partial<OperationContext>getPause?: (get: Getter) => booleangetClient?: (get: Getter) => Client}type AtomWithMutationOptions<Data = unknown,Variables extends AnyVariables = AnyVariables,> = {query: DocumentInput<Data, Variables>getClient?: (get: Getter) => Client}// 구독 타입은 AtomWithQueryOptions와 동일합니다.
Suspense 비활성화
더 안정적인 것으로 입증된 import { loadable } from "jotai/utils"
사용을 권장합니다. 하지만 여전히 Suspense를 사용하고 싶다면 다음과 같이 할 수 있습니다:
import { suspenseAtom } from 'jotai-urql'export const App = () => {// 앱 전체에 대해 Suspense를 비활성화useHydrateAtoms([[suspenseAtom, false]])return <Counter />}
유용한 헬퍼 훅
다음은 특이한 케이스를 처리하고, @tanstack/react-query
의 기본 동작과 유사하게 바인딩을 사용하는 헬퍼 훅입니다. 여기서는 Promise가 거부될 경우 에러를 에러로 처리하며, 주로 근처의 ErrorBoundaries에서 처리됩니다. 이 훅은 Suspense 버전에서만 유효합니다.
useQueryAtomData
data
를 깔끔하게 반환하며, 모든 오류 발생/재실행 케이스/예외 상황을 처리합니다.
타입이 재정의되어 data
는 절대 undefined
나 null
이 되지 않습니다. (단, 쿼리 자체의 반환 타입이 그럴 경우는 예외)
import type { AnyVariables, OperationResult } from '@urql/core'import { useAtom } from 'jotai'import type { AtomWithQuery } from 'jotai-urql'export const useQueryAtomData = <Data = unknown,Variables extends AnyVariables = AnyVariables,>(queryAtom: AtomWithQuery<Data, Variables>,) => {const [opResult, dispatch] = useAtom(queryAtom)if (opResult.error && opResult.stale) {use(// 여기서 트리를 일시 중단합니다. 이는 `network-only`로 리페치를 사용할 때// Error Boundary 재시도 로직에서만 트리가 일시 중단되지 않아 발생할 수 있는// "에러 발생 - Boundary에서 재시도 - 에러 발생 - Boundary에서 재시도"의 무한 루프를 방지합니다.// (Jotai URQL 바인딩에서만 해당).// eslint-disable-next-line promise/avoid-newnew Promise((resolve) => {setTimeout(resolve, 10000) // 이 타임아웃은 새로운 작업 결과가 도착할 때까지// 이 컴포넌트를 일시 중단시킵니다. 10초 후에는 컴포넌트를 다시 렌더링하려고 시도하고,// 다시 일시 중단되는 무한 루프에 빠질 수 있습니다.}),)}if (opResult.error) {throw opResult.error}if (!opResult.data) {throw Error('쿼리 데이터가 undefined입니다. 쿼리를 일시 중단했을 가능성이 있습니다. 그런 경우 `useQueryAtom`을 대신 사용하세요.',)}return [opResult.data, dispatch, opResult] as [Exclude<typeof opResult.data, undefined>,typeof dispatch,typeof opResult,]}// Promise가 해결되는 동안 트리를 일시 중단합니다 (React의 다음 버전에서는 필요 없음).function use(promise: Promise<any> | any) {if (promise.status === 'fulfilled') {return promise.value}if (promise.status === 'rejected') {throw promise.reason} else if (promise.status === 'pending') {throw promise} else {promise.status = 'pending'// eslint-disable-next-line promise/catch-or-return;(promise as Promise<any>).then((result: any) => {promise.status = 'fulfilled'promise.value = result},(reason: any) => {promise.status = 'rejected'promise.reason = reason},)throw promise}}
기본 데모
동일한 클라이언트 인스턴스를 아톰과 urql 프로바이더에 참조하기
동일한 urqlClient 객체를 참조하려면 프로젝트의 루트를 <Provider>
로 감싸고, UrqlProvider에 제공한 것과 동일한 urqlClient 값으로 clientAtom을 초기화해야 합니다.
이 단계를 생략하면 atomWithQuery
를 사용할 때마다 클라이언트를 지정해야 할 수 있습니다. 이제는 선택적 getClient
매개변수를 무시하고 컨텍스트에서 클라이언트를 사용할 수 있습니다.
import { Suspense } from 'react'import { Provider } from 'jotai/react'import { useHydrateAtoms } from 'jotai/react/utils'import { clientAtom } from 'jotai-urql'import {createClient,cacheExchange,fetchExchange,Provider as UrqlProvider,} from 'urql'const urqlClient = createClient({url: 'https://countries.trevorblades.com/',exchanges: [cacheExchange, fetchExchange],fetchOptions: () => {return { headers: {} }},})const HydrateAtoms = ({ children }) => {useHydrateAtoms([[clientAtom, urqlClient]])return children}export default function MyApp({ Component, pageProps }) {return (<UrqlProvider value={urqlClient}><Provider><HydrateAtoms><Suspense fallback="Loading..."><Component {...pageProps} /></Suspense></HydrateAtoms></Provider></UrqlProvider>)}