URQL
urql 提供了一个用于GraphQL查询、缓存和状态管理的工具包。
来自 概览文档:
urql 是一个高度可定制和多功能的 GraphQL 客户端,你可以随着成长添加诸如规范化缓存等功能。它旨在对 GraphQL 的新手易于使用,同时可扩展,以支持动态的单应用程序和高度定制的 GraphQL 基础设施。简而言之,urql 优先考虑易用性和适应性。
jotai-urql 是 URQL 的 Jotai 扩展库。它提供了一个整合了 URQL 的所有 GraphQL 功能的接口,允许你在现有的 Jotai 状态旁边利用这些功能。
安装
你需要安装 jotai-urql
,@urql/core
和 wonka
来使用这个扩展。
npm i jotai-urql @urql/core wonka
导出的函数
atomWithQuery
对应 client.queryatomWithMutation
对应 client.mutationatomWithSubscription
对应 client.subscription
基本使用
查询:
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}// 在这个点上,你必须使用可选链,因为数据可能在这个点上是未定义的(只在出错的情况下)return <>{operationResult.data?.count}</>}
变更:
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))}>增加</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}// Subscription 类型与 AtomWithQueryOptions 相同
禁用 suspense
推荐使用 import { loadable } from "jotai/utils"
,因为它更稳定。但是,如果你仍然想使用,以下是操作方法:
import { suspenseAtom } from 'jotai-urql'export const App = () => {// 我们为整个应用禁用 suspenseuseHydrateAtoms([[suspenseAtom, false]])return <Counter />}
有用的辅助 hook
这是一个辅助 hook,用于处理一个罕见的角落案例,并使这些绑定的使用类似于 @tanstack/react-query
的默认行为,其中错误被视为错误(在 Promise reject 的情况下)并且主要在附近的 ErrorBoundaries 中处理。只对挂起版本有效。
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(// 在这里我们挂起树。这只会在你在 Error Boundary 重试逻辑中使用 `network-only` 进行刷新的情况下触发,在这种情况下树不会挂起// 可能导致 "抛出 - 在边界重试 - 抛出 - 在边界重试" 的循环。// (仅在 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('查询数据未定义。可能你暂停了查询?在这种情况下使用 `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}}
基本演示
为 atoms 和 urql provider 引用同一客户端实例
为了确保你引用的是同一个 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>)}