react-query 源码学习-缓存是如何实现的

467 阅读3分钟

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的返回项

image.png

image.png

可以看到useHook返回的result代码如下:

  const result = observer.getOptimisticResult(defaultedOptions)、

查看observer.getOptimisticResult

image.png

查看关键代码:

const result = this.createResult(query, options);

查看createResult

image.png

在createResult里,看到我们要查的返回值是result.data,查找data是怎么赋值的,

image.png

可以看到,data最终是由query.state.data赋值的

image.png

返回查看query

image.png

终于找到我们最关注的queryKey这个配置到底是怎么用的,猜测是在queryCache这个类里设置了缓存:

image.png

可以看到缓存设置在了#queries里

image.png

# 表示私有属性

alt+鼠标左键查看#queries的相关代码

image.png

简单看看缓存操作

初始化在构造函数里,使用Map类型作为缓存数据结构

image.png

get和getAll函数

image.png

image.png

add函数 先查看是否存在,然后再添加,添加之后还调了个notify

image.png

remove函数

如果remove对象存在,则删除; 不知道为什么要调query,destory; 删除之后还要调notify。

image.png

由于我们最重要查看的是query.state.data是怎么来的,回到buildQuery的代码查看query类

image.png

Query类

从之前的分析看到,data是来自query.state.data。方法和属性太多了,我决定在outline里看一下有没有类似的方法。

在outline里找到一个叫setData的,可以看到他的数据是在dispatch方法里进行更新的

image.png

看一下dispatch的代码,可以看到this.state是由reducer方法返回的。

image.png

展开reducer进行查看:如果action.type是succeess

image.png

然后通过notifyManager.batch批量执行observers和cache的通知操作

看来query最底层的状态更新应该就是#dispatch方法, 但是到底是什么时候执行的fetch(执行http请求)呢??

寻找fetch执行的时机

现在不知道query是在什么时候执行了fetch,从而调用#dispatch方法来更新state。

我打算直接查看一下fetch的调用

image.png

可以看到在queryObserver的onSubscribe调用了

image.png

回到useBaseQuery,可以发现调用了observer的subscribe

image.png

进一步查看,发现observer的subscribe就是调用了onSubscribe

image.png

这下就很清楚到底是在哪里调用了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),猜测应该是通过发布-订阅模式去处理各个状态的更新,具体下个坑再填吧哈哈