背景
之前用过一些redux toolkit,现在项目再次引入了 toolkit,但是它的官方文档是英文,机翻不是很友好,读起来也不是很方便,于是我总结了一下它在项目里面的基本用法。
配置
这里基本按照代码里面的进行配置即可。
使用之前,按照官网的说明安装即可
npm install @reduxjs/toolkit react-redux
新建store文件
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {},
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
store 提供给react
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
新建 API
toolkit提供了createApi函数来新建 API。
它的作用是自动生成你定义的每一个query和mutation的 react hook。
官网实例如下:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Pokemon } from './types'
// Define a service using a base URL and expected endpoints
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 hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetPokemonByNameQuery } = pokemonApi
baseQuery
这个属性是用来定义一些公共的查询操作,我在这里做了全局错误的处理,当然也可以在这里处理没有登录的全局配置、或者请求失败自动重试等等。
basequery也可以和axios结合使用,官方文档提供了示例。
import { createApi } from '@reduxjs/toolkit/query'
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
import axios from 'axios'
import type { AxiosRequestConfig, AxiosError } from 'axios'
const axiosBaseQuery =
(
{ baseUrl }: { baseUrl: string } = { baseUrl: '' }
): BaseQueryFn<
{
url: string
method: AxiosRequestConfig['method']
data?: AxiosRequestConfig['data']
params?: AxiosRequestConfig['params']
},
unknown,
unknown
> =>
async ({ url, method, data, params }) => {
try {
const result = await axios({ url: baseUrl + url, method, data, params })
return { data: result.data }
} catch (axiosError) {
let err = axiosError as AxiosError
return {
error: {
status: err.response?.status,
data: err.response?.data || err.message,
},
}
}
}
const api = createApi({
baseQuery: axiosBaseQuery({
baseUrl: 'https://example.com',
}),
endpoints(build) {
return {
query: build.query({ query: () => ({ url: '/query', method: 'get' }) }),
mutation: build.mutation({
query: () => ({ url: '/mutation', method: 'post' }),
}),
}
},
})
这里需要注意的是,因为是自定义return的数据结构,所以类型要和toolkit定义的保持一致。
endpoints
这里 retrun的就是所有的请求。
我在项目中使用的时候,一般createApi 创建的是一个类型的服务,endpoints 返回的就是该服务的所有请求,可以是get,post等等。
这里的build可以使用query和 mutation。
它们的区别官方文档是这么说的:
一般建议您只对检索数据的请求使用查询。对于任何改变服务器上的数据或可能使缓存无效的事情,您应该使用mutation。
我理解就是如果只是查询数据,就使用 query ,如果涉及到修改数据, 就使用 mutation。
transform
可选的配置,可以帮助转换我们收到的数据格式
tags
可选的配置,可以帮助我们进行缓存,相关的有tagTypes,providesTags,invalidatesTags这几个属性。
设置了tagTypes属性后,就可以使用providesTags让tag生效,也可以使用invalidatesTags使tag无效。
providesTags可以配置如下类型的值:
1. `['Post']` - 等于 `2`
2. `[{ type: 'Post' }]` - 等于 `1`
3. `[{ type: 'Post', id: 1 }]`
4. `(result, error, arg) => ['Post']` - 等于 `5`
5. `(result, error, arg) => [{ type: 'Post' }]` - 等于 `4`
6. `(result, error, arg) => [{ type: 'Post', id: 1 }]`
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts' as const, id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
})
根据上例可以看到,一般 id 是从后端的数据里面取的。
invalidatesTags的类型和providesTags是一样的,是给mutation的 endpoints 使用的,用来确定应从缓存中重新获取或删除哪些缓存数据。
最后需要在该文件导出我们要使用的接口的hook
toolkit 有五种hook。
1.useQuery
与useQuerySubscription和useQueryState是主要的钩子。自动触发从端点获取数据,将组件“订阅”到缓存数据,并从 Redux 存储中读取请求状态和缓存数据。
2.useQuerySubscription
返回一个refetch函数并接受所有钩子选项。自动触发从端点获取数据,并将组件“订阅”到缓存数据。
3.useQueryState
返回查询状态并接受skip和selectFromResult。从 Redux 存储中读取请求状态和缓存数据。
4.useLazyQuery
返回一个包含trigger函数、查询结果和最后承诺信息的元组。类似于useQuery,但可以手动控制何时发生数据获取。注意:如果缓存数据已经存在,如果您想跳过发出请求,则该trigger函数采用第二个参数。preferCacheValue?: boolean
5.useLazyQuerySubscription
返回一个带有trigger函数和最后承诺信息的元组。类似于useQuerySubscription,但可以手动控制何时发生数据获取。注意:如果缓存数据已经存在,如果您想跳过发出请求,则该trigger函数采用第二个参数。preferCacheValue?: boolean
每次新建一个接口,都要对应的在store文件配置configureStore函数的reducer和middleware属性
import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { setupListeners } from '@reduxjs/toolkit/query'
import { pokemonApi } from './services/pokemon'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[pokemonApi.reducerPath]: pokemonApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(pokemonApi.middleware),
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
最后就是在对应的组件里面使用我们create的 API
hook返回的内容如下:
data- 最新返回的结果,不管 hook arg,如果存在的话。currentData- 当前 hook arg 的最新返回结果(如果存在)。error- 错误结果(如果存在)。isUninitialized- 当为真时,表示查询还没有开始。isLoading- 当为真时,表示查询当前是第一次加载,还没有数据。这将true适用于第一个请求,但不适用于后续请求。isFetching- 如果为真,则表示查询当前正在获取,但可能包含来自较早请求的数据。这将true适用于第一个请求以及后续请求。isSuccess- 如果为真,则表示查询具有来自成功请求的数据。isError- 当为真时,表示查询处于某种error状态。refetch- 强制重新获取查询的功能