JotaiJotai

Jotai
React 原始而灵活的状态管理

URQL

urql 提供了一个用于GraphQL查询、缓存和状态管理的工具包。

来自 概览文档

urql 是一个高度可定制和多功能的 GraphQL 客户端,你可以随着成长添加诸如规范化缓存等功能。它旨在对 GraphQL 的新手易于使用,同时可扩展,以支持动态的单应用程序和高度定制的 GraphQL 基础设施。简而言之,urql 优先考虑易用性和适应性。

jotai-urql 是 URQL 的 Jotai 扩展库。它提供了一个整合了 URQL 的所有 GraphQL 功能的接口,允许你在现有的 Jotai 状态旁边利用这些功能。

安装

你需要安装 jotai-urql@urql/corewonka 来使用这个扩展。

npm i jotai-urql @urql/core wonka

导出的函数

基本使用

查询:

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>
<button
onClick={() =>
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) => Variables
getContext?: (get: Getter) => Partial<OperationContext>
getPause?: (get: Getter) => boolean
getClient?: (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 = () => {
// 我们为整个应用禁用 suspense
useHydrateAtoms([[suspenseAtom, false]])
return <Counter />
}

有用的辅助 hook

这是一个辅助 hook,用于处理一个罕见的角落案例,并使这些绑定的使用类似于 @tanstack/react-query 的默认行为,其中错误被视为错误(在 Promise reject 的情况下)并且主要在附近的 ErrorBoundaries 中处理。只对挂起版本有效。

useQueryAtomData

在解析后整齐地返回 data + 处理所有的错误抛出/重新执行案例/角落案例。 注意类型被覆盖,所以 data 永远不会是 undefinednull(除非这是查询本身的预期返回类型)

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-new
new 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>
)
}