框架
版本

迁移到 React Query 3

以前的 React Query 版本非常棒,带来了令人惊叹的新功能、更多的魔力以及整体更好的库体验。它们也带来了大规模的采用以及大量的改进(问题/贡献),并揭示了一些需要进一步完善才能使库变得更好的地方。v3 包含了这些完善。

概述

  • 更具可扩展性和可测试性的缓存配置。
  • 更好的 SSR 支持。
  • 数据滞后(以前是 usePaginatedQuery)随处可用!
  • 双向无限查询。
  • 查询数据选择器!
  • 在使用前完全配置查询和/或修改的默认值。
  • 更精细的可选渲染优化。
  • 新的 useQueries 钩子!(变长并行查询执行)
  • useIsFetching() 钩子支持查询过滤器!
  • 修改的重试/离线/重播支持。
  • 在 React 之外观察查询/修改。
  • 在任何你想使用 React Query 核心逻辑的地方使用它!
  • 通过 react-query/devtools 捆绑/同地 Devtools
  • 将缓存持久化到 Web 存储(通过 react-query/persistQueryClient-experimentalreact-query/createWebStoragePersistor-experimental 进行实验性支持)

重大更改

QueryCache 已拆分为 QueryClient 和更底层的 QueryCacheMutationCache 实例。

QueryCache 包含所有查询,MutationCache 包含所有修改,而 QueryClient 可用于设置配置并与它们交互。

这有一些好处:

  • 允许不同类型的缓存。
  • 多个具有不同配置的客户端可以使用同一个缓存。
  • 客户端可用于跟踪查询,这可用于 SSR 上的共享缓存。
  • 客户端 API 更侧重于通用用法。
  • 更容易测试各个组件。

创建 new QueryClient() 时,如果您不提供 QueryCacheMutationCache,它们会自动为您创建。

tsx
import { QueryClient } from 'react-query'

const queryClient = new QueryClient()
import { QueryClient } from 'react-query'

const queryClient = new QueryClient()

ReactQueryConfigProviderReactQueryCacheProvider 都已被 QueryClientProvider 替换。

现在可以在 QueryClient 中指定查询和修改的默认选项。

请注意,现在是 defaultOptions 而不是 defaultConfig。

tsx
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // query options
    },
    mutations: {
      // mutation options
    },
  },
})
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // query options
    },
    mutations: {
      // mutation options
    },
  },
})

QueryClientProvider 组件现在用于将 QueryClient 连接到您的应用程序。

tsx
import { QueryClient, QueryClientProvider } from 'react-query'

const queryClient = new QueryClient()

function App() {
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
import { QueryClient, QueryClientProvider } from 'react-query'

const queryClient = new QueryClient()

function App() {
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}

默认的 QueryCache 不再存在。这次是真的!

如前所述,由于已废弃,主包不再创建或导出默认的 QueryCache您必须通过 new QueryClient()new QueryCache() 自行创建(然后您可以将其传递给 new QueryClient({ queryCache })

已废弃的 makeQueryCache 工具已移除。

它已经存在很久了,但最终还是被移除了 :)

QueryCache.prefetchQuery() 已移至 QueryClient.prefetchQuery()

新的 QueryClient.prefetchQuery() 函数是异步的,但不返回查询数据。如果需要数据,请使用新的 QueryClient.fetchQuery() 函数。

tsx
// Prefetch a query:
await queryClient.prefetchQuery('posts', fetchPosts)

// Fetch a query:
try {
  const data = await queryClient.fetchQuery('posts', fetchPosts)
} catch (error) {
  // Error handling
}
// Prefetch a query:
await queryClient.prefetchQuery('posts', fetchPosts)

// Fetch a query:
try {
  const data = await queryClient.fetchQuery('posts', fetchPosts)
} catch (error) {
  // Error handling
}

ReactQueryErrorResetBoundaryQueryCache.resetErrorBoundaries() 已被 QueryErrorResetBoundaryuseQueryErrorResetBoundary() 替换。

它们共同提供了与以前相同的体验,但增加了控制权,可以选择要重置的组件树。欲了解更多信息,请参阅。

QueryCache.getQuery() 已被 QueryCache.find() 替换。

现在应该使用 QueryCache.find() 从缓存中查找单个查询。

QueryCache.getQueries() 已移至 QueryCache.findAll()

现在应该使用 QueryCache.findAll() 从缓存中查找多个查询。

QueryCache.isFetching 已移至 QueryClient.isFetching()

请注意,它现在是一个函数而不是一个属性。

useQueryCache 钩子已被 useQueryClient 钩子替换。

它返回其组件树的 queryClient,除了重命名之外,应该不需要太多修改。

查询键的部分不再自动传播到查询函数。

现在建议使用内联函数将参数传递给查询函数。

tsx
// Old
useQuery(['post', id], (_key, id) => fetchPost(id))

// New
useQuery(['post', id], () => fetchPost(id))
// Old
useQuery(['post', id], (_key, id) => fetchPost(id))

// New
useQuery(['post', id], () => fetchPost(id))

如果您仍然坚持不使用内联函数,可以使用新传递的 QueryFunctionContext

tsx
useQuery(['post', id], (context) => fetchPost(context.queryKey[1]))
useQuery(['post', id], (context) => fetchPost(context.queryKey[1]))

无限查询页面参数现在通过 QueryFunctionContext.pageParam 传递。

它们以前是作为查询函数中最后一个查询键参数添加的,但这对于某些模式来说很难。

tsx
// Old
useInfiniteQuery(['posts'], (_key, pageParam = 0) => fetchPosts(pageParam))

// New
useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPosts(pageParam))
// Old
useInfiniteQuery(['posts'], (_key, pageParam = 0) => fetchPosts(pageParam))

// New
useInfiniteQuery(['posts'], ({ pageParam = 0 }) => fetchPosts(pageParam))

usePaginatedQuery() 已被移除,取而代之的是 keepPreviousData 选项。

新的 keepPreviousData 选项适用于 useQueryuseInfiniteQuery,并将对您的数据产生相同的“滞后”效果。

tsx
import { useQuery } from 'react-query'

function Page({ page }) {
  const { data } = useQuery(['page', page], fetchPage, {
    keepPreviousData: true,
  })
}
import { useQuery } from 'react-query'

function Page({ page }) {
  const { data } = useQuery(['page', page], fetchPage, {
    keepPreviousData: true,
  })
}

useInfiniteQuery() 现在是双向的。

useInfiniteQuery() 接口已更改,以完全支持双向无限列表。

  • options.getFetchMore 已重命名为 options.getNextPageParam
  • queryResult.canFetchMore 已重命名为 queryResult.hasNextPage
  • queryResult.fetchMore 已重命名为 queryResult.fetchNextPage
  • queryResult.isFetchingMore 已重命名为 queryResult.isFetchingNextPage
  • 添加了 options.getPreviousPageParam 选项。
  • 添加了 queryResult.hasPreviousPage 属性。
  • 添加了 queryResult.fetchPreviousPage 属性。
  • 添加了 queryResult.isFetchingPreviousPage
  • 无限查询的 data 现在是一个对象,包含 pages 和用于获取这些页面的 pageParams{ pages: [data, data, data], pageParams: [...]}

一个方向。

tsx
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )

两个方向。

tsx
const {
  data,
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
} = useInfiniteQuery(
  'projects',
  ({ pageParam = 0 }) => fetchProjects(pageParam),
  {
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
  },
)
const {
  data,
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
} = useInfiniteQuery(
  'projects',
  ({ pageParam = 0 }) => fetchProjects(pageParam),
  {
    getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor,
  },
)

一个方向反转。

tsx
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      select: (data) => ({
        pages: [...data.pages].reverse(),
        pageParams: [...data.pageParams].reverse(),
      }),
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
  useInfiniteQuery(
    'projects',
    ({ pageParam = 0 }) => fetchProjects(pageParam),
    {
      select: (data) => ({
        pages: [...data.pages].reverse(),
        pageParams: [...data.pageParams].reverse(),
      }),
      getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
    },
  )

无限查询数据现在包含用于获取这些页面的页面数组和 pageParams。

这允许更容易地操作数据和页面参数,例如,删除第一页数据及其参数。

tsx
queryClient.setQueryData(['projects'], (data) => ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))
queryClient.setQueryData(['projects'], (data) => ({
  pages: data.pages.slice(1),
  pageParams: data.pageParams.slice(1),
}))

useMutation 现在返回一个对象而不是一个数组。

尽管旧的方式带给我们第一次发现 useState 时那种温暖模糊的感觉,但它们并没有持续多久。现在修改的返回是一个单一的对象。

tsx
// Old:
const [mutate, { status, reset }] = useMutation()

// New:
const { mutate, status, reset } = useMutation()
// Old:
const [mutate, { status, reset }] = useMutation()

// New:
const { mutate, status, reset } = useMutation()

mutation.mutate 不再返回 Promise。

  • [mutate] 变量已更改为 mutation.mutate 函数。
  • 添加了 mutation.mutateAsync 函数。

我们收到了很多关于此行为的问题,因为用户期望 Promise 像常规 Promise 一样运行。

因此,mutate 函数现在被拆分为 mutatemutateAsync 函数。

在使用回调时可以使用 mutate 函数。

tsx
const { mutate } = useMutation({ mutationFn: addTodo })

mutate('todo', {
  onSuccess: (data) => {
    console.log(data)
  },
  onError: (error) => {
    console.error(error)
  },
  onSettled: () => {
    console.log('settled')
  },
})
const { mutate } = useMutation({ mutationFn: addTodo })

mutate('todo', {
  onSuccess: (data) => {
    console.log(data)
  },
  onError: (error) => {
    console.error(error)
  },
  onSettled: () => {
    console.log('settled')
  },
})

在使用 async/await 时可以使用 mutateAsync 函数。

tsx
const { mutateAsync } = useMutation({ mutationFn: addTodo })

try {
  const data = await mutateAsync('todo')
  console.log(data)
} catch (error) {
  console.error(error)
} finally {
  console.log('settled')
}
const { mutateAsync } = useMutation({ mutationFn: addTodo })

try {
  const data = await mutateAsync('todo')
  console.log(data)
} catch (error) {
  console.error(error)
} finally {
  console.log('settled')
}

useQuery 的对象语法现在使用折叠配置。

tsx
// Old:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  config: { staleTime: Infinity },
})

// New:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  staleTime: Infinity,
})
// Old:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  config: { staleTime: Infinity },
})

// New:
useQuery({
  queryKey: 'posts',
  queryFn: fetchPosts,
  staleTime: Infinity,
})

如果设置,QueryOptions.enabled 选项必须是布尔值 (true/false)

现在,enabled 查询选项只有当值为 false 时才会禁用查询。如果需要,可以使用 !!userIdBoolean(userId) 来强制转换值,如果传递非布尔值,则会抛出便捷错误。

QueryOptions.initialStale 选项已移除。

initialStale 查询选项已移除,初始数据现在被视为常规数据。这意味着如果提供了 initialData,查询将默认在挂载时重新获取。如果您不想立即重新获取,可以定义一个 staleTime

QueryOptions.forceFetchOnMount 选项已替换为 refetchOnMount: 'always'

说实话,我们累积了太多 refetchOn____ 选项,所以这应该能清理一下。

QueryOptions.refetchOnMount 选项现在只适用于其父组件,而不是所有查询观察者。

refetchOnMount 设置为 false 时,会阻止任何其他组件在挂载时重新获取。在版本 3 中,只有设置了该选项的组件才不会在挂载时重新获取。

QueryOptions.queryFnParamsFilter 已被移除,取而代之的是新的 QueryFunctionContext 对象。

queryFnParamsFilter 选项已移除,因为查询函数现在获取 QueryFunctionContext 对象而不是查询键。

参数仍然可以在查询函数本身中进行过滤,因为 QueryFunctionContext 也包含查询键。

QueryOptions.notifyOnStatusChange 选项已被新的 notifyOnChangePropsnotifyOnChangePropsExclusions 选项取代。

借助这些新选项,可以精细配置组件何时重新渲染。

仅当 dataerror 属性更改时才重新渲染。

tsx
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangeProps: ['data', 'error'],
  })
  return <div>Username: {data.username}</div>
}
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangeProps: ['data', 'error'],
  })
  return <div>Username: {data.username}</div>
}

isStale 属性更改时,阻止重新渲染。

tsx
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangePropsExclusions: ['isStale'],
  })
  return <div>Username: {data.username}</div>
}
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    notifyOnChangePropsExclusions: ['isStale'],
  })
  return <div>Username: {data.username}</div>
}

QueryResult.clear() 函数已重命名为 QueryResult.remove()

虽然它被称为 clear,但它实际上只是将查询从缓存中移除。现在名称与功能匹配。

QueryResult.updatedAt 属性已拆分为 QueryResult.dataUpdatedAtQueryResult.errorUpdatedAt 属性。

由于数据和错误可以同时存在,updatedAt 属性已拆分为 dataUpdatedAterrorUpdatedAt

setConsole() 已被新的 setLogger() 函数替换。

tsx
import { setLogger } from 'react-query'

// Log with Sentry
setLogger({
  error: (error) => {
    Sentry.captureException(error)
  },
})

// Log with Winston
setLogger(winston.createLogger())
import { setLogger } from 'react-query'

// Log with Sentry
setLogger({
  error: (error) => {
    Sentry.captureException(error)
  },
})

// Log with Winston
setLogger(winston.createLogger())

React Native 不再需要覆盖日志器。

为了防止在 React Native 中查询失败时显示错误屏幕,有必要手动更改控制台。

tsx
import { setConsole } from 'react-query'

setConsole({
  log: console.log,
  warn: console.warn,
  error: console.warn,
})
import { setConsole } from 'react-query'

setConsole({
  log: console.log,
  warn: console.warn,
  error: console.warn,
})

在版本 3 中,当在 React Native 中使用 React Query 时,这会自动完成

Typescript

QueryStatus 已从 枚举 更改为 联合类型

因此,如果您之前根据 QueryStatus 枚举属性检查查询或修改的状态属性,现在您必须根据枚举先前为每个属性保存的字符串文字进行检查。

因此,您必须将枚举属性更改为等效的字符串文字,如下所示:

  • QueryStatus.Idle -> 'idle'
  • QueryStatus.Loading -> 'loading'
  • QueryStatus.Error -> 'error'
  • QueryStatus.Success -> 'success'

以下是您必须进行的更改示例:

tsx
- import { useQuery, QueryStatus } from 'react-query'; // [!code --]
+ import { useQuery } from 'react-query'; // [!code ++]

const { data, status } = useQuery(['post', id], () => fetchPost(id))

- if (status === QueryStatus.Loading) { // [!code --]
+ if (status === 'loading') { // [!code ++]
  ...
}

- if (status === QueryStatus.Error) { // [!code --]
+ if (status === 'error') { // [!code ++]
  ...
}
- import { useQuery, QueryStatus } from 'react-query'; // [!code --]
+ import { useQuery } from 'react-query'; // [!code ++]

const { data, status } = useQuery(['post', id], () => fetchPost(id))

- if (status === QueryStatus.Loading) { // [!code --]
+ if (status === 'loading') { // [!code ++]
  ...
}

- if (status === QueryStatus.Error) { // [!code --]
+ if (status === 'error') { // [!code ++]
  ...
}

新特性

查询数据选择器

useQueryuseInfiniteQuery 钩子现在有一个 select 选项,用于选择或转换查询结果的部分。

tsx
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    select: (user) => user.username,
  })
  return <div>Username: {data}</div>
}
import { useQuery } from 'react-query'

function User() {
  const { data } = useQuery(['user'], fetchUser, {
    select: (user) => user.username,
  })
  return <div>Username: {data}</div>
}

notifyOnChangeProps 选项设置为 ['data', 'error'],仅在选定数据更改时重新渲染。

useQueries() 钩子,用于变长并行查询执行。

希望能在循环中运行 useQuery 吗?Hook 规则不允许,但有了新的 useQueries() 钩子,您可以!

tsx
import { useQueries } from 'react-query'

function Overview() {
  const results = useQueries([
    { queryKey: ['post', 1], queryFn: fetchPost },
    { queryKey: ['post', 2], queryFn: fetchPost },
  ])
  return (
    <ul>
      {results.map(({ data }) => data && <li key={data.id}>{data.title})</li>)}
    </ul>
  )
}
import { useQueries } from 'react-query'

function Overview() {
  const results = useQueries([
    { queryKey: ['post', 1], queryFn: fetchPost },
    { queryKey: ['post', 2], queryFn: fetchPost },
  ])
  return (
    <ul>
      {results.map(({ data }) => data && <li key={data.id}>{data.title})</li>)}
    </ul>
  )
}

重试/离线修改。

默认情况下,React Query 不会在出错时重试修改,但可以通过 retry 选项实现。

tsx
const mutation = useMutation({
  mutationFn: addTodo,
  retry: 3,
})
const mutation = useMutation({
  mutationFn: addTodo,
  retry: 3,
})

如果修改因设备离线而失败,它们将在设备重新连接时以相同的顺序重试。

持久化修改。

修改现在可以持久化到存储并在以后恢复。更多信息可以在修改文档中找到。

QueryObserver

可以使用 QueryObserver 创建和/或观察查询。

tsx
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})
const observer = new QueryObserver(queryClient, { queryKey: 'posts' })

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})

InfiniteQueryObserver

可以使用 InfiniteQueryObserver 创建和/或观察无限查询。

tsx
const observer = new InfiniteQueryObserver(queryClient, {
  queryKey: 'posts',
  queryFn: fetchPosts,
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})
const observer = new InfiniteQueryObserver(queryClient, {
  queryKey: 'posts',
  queryFn: fetchPosts,
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})

QueriesObserver

可以使用 QueriesObserver 创建和/或观察多个查询。

tsx
const observer = new QueriesObserver(queryClient, [
  { queryKey: ['post', 1], queryFn: fetchPost },
  { queryKey: ['post', 2], queryFn: fetchPost },
])

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})
const observer = new QueriesObserver(queryClient, [
  { queryKey: ['post', 1], queryFn: fetchPost },
  { queryKey: ['post', 2], queryFn: fetchPost },
])

const unsubscribe = observer.subscribe((result) => {
  console.log(result)
  unsubscribe()
})

为特定查询设置默认选项。

可以使用 QueryClient.setQueryDefaults() 方法为特定查询设置默认选项。

tsx
queryClient.setQueryDefaults(['posts'], { queryFn: fetchPosts })

function Component() {
  const { data } = useQuery(['posts'])
}
queryClient.setQueryDefaults(['posts'], { queryFn: fetchPosts })

function Component() {
  const { data } = useQuery(['posts'])
}

为特定修改设置默认选项。

可以使用 QueryClient.setMutationDefaults() 方法为特定修改设置默认选项。

tsx
queryClient.setMutationDefaults(['addPost'], { mutationFn: addPost })

function Component() {
  const { mutate } = useMutation({ mutationKey: ['addPost'] })
}
queryClient.setMutationDefaults(['addPost'], { mutationFn: addPost })

function Component() {
  const { mutate } = useMutation({ mutationKey: ['addPost'] })
}

useIsFetching()

useIsFetching() 钩子现在接受过滤器,例如,可以仅为特定类型的查询显示加载器。

tsx
const fetches = useIsFetching({ queryKey: ['posts'] })
const fetches = useIsFetching({ queryKey: ['posts'] })

核心分离。

React Query 的核心现在已完全与 React 分离,这意味着它也可以独立使用或在其他框架中使用。使用 react-query/core 入口点仅导入核心功能。

tsx
import { QueryClient } from 'react-query/core'
import { QueryClient } from 'react-query/core'

Devtools 现在是主仓库和 npm 包的一部分。

devtools 现在包含在 react-query 包本身中,导入路径为 react-query/devtools。只需将 react-query-devtools 导入替换为 react-query/devtools