跳到主要内容

 

RTK Query 快速入门

你将学到什么
  • 如何设置和使用 Redux Toolkit 的 "RTK Query" 数据获取功能
先决条件

介绍

欢迎来到 Redux Toolkit Query 教程!本教程将简要介绍 Redux Toolkit 的 "RTK Query" 数据获取功能,并教你如何正确开始使用它

信息

对于 RTK Query 的更深入教程,请参阅 Redux 核心文档网站上的完整 "Redux Essentials" 教程

RTK Query 是一个高级的数据获取和缓存工具,旨在简化在 web 应用程序中加载数据的常见情况。RTK Query 本身是建立在 Redux Toolkit 核心之上的,并利用 RTK 的 APIs,如 createSlicecreateAsyncThunk 来实现其功能。

RTK Query 包含在 @reduxjs/toolkit 包中作为一个额外的插件。当你使用 Redux Toolkit 时,你不需要使用 RTK Query APIs,但我们认为许多用户将从他们的应用程序中的 RTK Query 数据获取和缓存中受益。

如何阅读本教程

对于本教程,我们假设你正在使用 Redux Toolkit 与 React,但你也可以将其与其他 UI 层一起使用。示例基于 典型的 Create-React-App 文件结构,其中所有的应用程序代码都在 src 中,但这些模式可以适应你正在使用的任何项目或文件设置。

设置你的存储和 API 服务

为了看到 RTK Query 如何工作,让我们通过一个基本的使用示例来进行演示。对于这个示例,我们假设你正在使用 React,并希望使用 RTK Query 自动生成的 React 钩子。

创建一个 API 服务

首先,我们将创建一个查询公开可用的 PokeAPI 的服务定义。

src/services/pokemon.ts
// 文件: services/types.ts noEmit
export type Pokemon = {}

// 文件: services/pokemon.ts
// 需要使用 React-specific 的入口点来导入 createApi
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Pokemon } from './types'

// 使用基础 URL 和预期的端点定义服务
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query<Pokemon, string>({
query: (name) => `pokemon/${name}`,
}),
}),
})

// 导出用于在功能组件中使用的钩子,这些钩子是
// 根据定义的端点自动生成的
export const { useGetPokemonByNameQuery } = pokemonApi

使用 RTK Query,你通常在一个地方定义你的整个 API 定义。这可能与你在其他库如 swrreact-query 中看到的有所不同,有几个原因。我们的观点是,与在你的应用程序的不同文件中有 X 数量的自定义钩子相比,当它们都在一个中心位置时,更容易跟踪请求、缓存失效和一般应用配置的行为。

提示

通常,你应该只有一个 API 切片,每个你的应用需要通信的基础 URL。例如,如果你的网站从 /api/posts/api/users 获取数据,你将有一个单一的 API 切片,以 /api/ 作为基础 URL,并为 postsusers 定义单独的端点。这使你能够有效地利用 自动重新获取 通过定义跨端点的 标签 关系。

出于可维护性的目的,你可能希望在多个文件中分割端点定义,同时仍然保持一个包含所有这些端点的单一 API 切片。请参阅 代码分割 了解如何使用 injectEndpoints 属性将其他文件中的 API 端点注入到单一的 API 切片定义中。

将服务添加到你的存储中

RTKQ 服务生成一个应该包含在 Redux 根 reducer 中的 "slice reducer",以及处理数据获取的自定义中间件。这两者都需要添加到 Redux 存储中。

src/store.ts
// 文件: services/pokemon.ts noEmit
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query({
query: (name: string) => `pokemon/${name}`,
}),
}),
})

// 文件: store.ts
import { configureStore } from '@reduxjs/toolkit'
// 或者从 '@reduxjs/toolkit/query/react' 导入
import { setupListeners } from '@reduxjs/toolkit/query'
import { pokemonApi } from './services/pokemon'

export const store = configureStore({
reducer: {
// 将生成的 reducer 添加为特定的顶级 slice
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
// 添加 api 中间件启用缓存,失效,轮询,
// 和其他 `rtk-query` 的有用特性。
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(pokemonApi.middleware),
})

// 可选,但对于 refetchOnFocus/refetchOnReconnect 行为是必需的
// 查看 `setupListeners` 文档 - 接受一个可选的回调作为第二个参数进行自定义
setupListeners(store.dispatch)

使用 Provider 包裹你的应用程序

如果你还没有这样做,遵循为你的 React 应用程序组件树提供 Redux 存储的标准模式:

src/index.tsx
// 文件: App.tsx noEmit
import React from 'react'
export default function App() {
return <div>...</div>
}

// 文件: store.ts noEmit
import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
reducer: {},
})

// 文件: index.tsx
import * as React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'

import App from './App'
import { store } from './store'

const rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)

在组件中使用查询

一旦定义了服务,你就可以导入钩子来发起请求。

src/App.tsx
// 文件: services/pokemon.ts noEmit
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query({
query: (name: string) => `pokemon/${name}`,
}),
}),
})

export const { useGetPokemonByNameQuery } = pokemonApi

// 文件: App.tsx
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'

export default function App() {
// 使用查询钩子自动获取数据并返回查询值
const { data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
// 也可以在生成的端点下访问单独的钩子:
// const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery('bulbasaur')

return (
<div className="App">
{error ? (
<>哦,出错了</>
) : isLoading ? (
<>加载中...</>
) : data ? (
<>
<h3>{data.species.name}</h3>
<img src={data.sprites.front_shiny} alt={data.species.name} />
</>
) : null}
</div>
)
}

在发起请求时,你可以通过多种方式跟踪状态。你可以始终检查 datastatuserror 来确定要渲染的正确 UI。此外,useQuery 还提供了像 isLoadingisFetchingisSuccessisError 这样的实用布尔值,用于最新的请求。

基础示例

好的,这很有趣... 但是如果你想同时显示多个宠物小精灵怎么办?如果多个组件加载同一个宠物小精灵会发生什么?

高级示例

RTK Query 确保订阅同一查询的任何组件都将始终使用相同的数据。RTK Query 自动去重请求,因此你无需担心检查正在进行的请求和你端的性能优化。让我们评估下面的沙箱 - 确保检查你浏览器的开发者工具中的网络面板。你会看到 3 个请求,尽管有 4 个订阅的组件 - bulbasaur 只发起一个请求,两个组件之间的加载状态是同步的。为了好玩,尝试将下拉框的值从 Off 改为 1s,看看当查询重新运行时这种行为是否继续。