useQuery特性介绍
首先要知道react-query的重要特性是什么:
- Caching... (possibly the hardest thing to do in programming)
- 缓存... (可能是编程中最难做的事情)
- Deduping multiple requests for the same data into a single request
- 将同一数据的多个请求欺骗为单个请求
- Updating "out of date" data in the background
- 在后台更新“过期”数据
- Knowing when data is "out of date"
- 知道数据何时“过期”
- Reflecting updates to data as quickly as possible
- 尽快反映对数据的更新
- Performance optimizations like pagination and lazy loading data
- 性能优化,如分页和延迟加载数据
- Managing memory and garbage collection of server state
- 管理服务器状态的内存和垃圾收集
- Memoizing query results with structural sharing
- 使用结构共享来记录查询结果
我打算查看useQuery这个核心hook。 我感兴趣的是,他是怎么缓存数据的。
用例如下:
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then(
(res) => res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
查看useQuery
查看useQuery的返回项
可以看到useHook返回的result代码如下:
const result = observer.getOptimisticResult(defaultedOptions)、
查看observer.getOptimisticResult
查看关键代码:
const result = this.createResult(query, options);
查看createResult
在createResult里,看到我们要查的返回值是result.data,查找data是怎么赋值的,
可以看到,data最终是由query.state.data赋值的
返回查看query
终于找到我们最关注的queryKey这个配置到底是怎么用的,猜测是在queryCache这个类里设置了缓存:
可以看到缓存设置在了#queries里
#表示私有属性
alt+鼠标左键查看#queries的相关代码
简单看看缓存操作
初始化在构造函数里,使用Map类型作为缓存数据结构
get和getAll函数
add函数 先查看是否存在,然后再添加,添加之后还调了个notify
remove函数
如果remove对象存在,则删除; 不知道为什么要调query,destory; 删除之后还要调notify。
由于我们最重要查看的是query.state.data是怎么来的,回到buildQuery的代码查看query类
Query类
从之前的分析看到,data是来自query.state.data。方法和属性太多了,我决定在outline里看一下有没有类似的方法。
在outline里找到一个叫setData的,可以看到他的数据是在dispatch方法里进行更新的
看一下dispatch的代码,可以看到this.state是由reducer方法返回的。
展开reducer进行查看:如果action.type是succeess
然后通过notifyManager.batch批量执行observers和cache的通知操作
看来query最底层的状态更新应该就是#dispatch方法, 但是到底是什么时候执行的fetch(执行http请求)呢??
寻找fetch执行的时机
现在不知道query是在什么时候执行了fetch,从而调用#dispatch方法来更新state。
我打算直接查看一下fetch的调用
可以看到在queryObserver的onSubscribe调用了
回到useBaseQuery,可以发现调用了observer的subscribe
进一步查看,发现observer的subscribe就是调用了onSubscribe
这下就很清楚到底是在哪里调用了fetch了
结论
缓存是通过queryCache(Map类型)来缓存不同的query,通过queryKey进行读取。
然后在useQuery返回数据的时候会直接返回query.state.data 而query的query.state.data是通过#dispatch去设置的
当客户端使用useQuery的时候,useQuery会新建一个observer,并且使用useSyncExternalStore订阅(subscribe)observer。在subscribe的时候,observer会调用fetch方法, 发出http请求,然后调用#dispatch更新状态。
由于useQuery的核心代码是使用js进行编写的,没有依赖react的响应式(useState),猜测应该是通过发布-订阅模式去处理各个状态的更新,具体下个坑再填吧哈哈