[翻译][学习]Redux Toolkit基本使用

498 阅读5分钟

背景

之前用过一些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。

image.png

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- 强制重新获取查询的功能

refer

  1. redux-toolkit.js.org/rtk-query/o…
  2. redux-toolkit.js.org/tutorials/q…