likes
comments
collection
share

企业级项目如何使用redux-toolkit 和RTKQuery

作者站长头像
站长
· 阅读数 23

你会学到什么

  1. 如何使用redux-toolkit管理项目的状态
  2. 如何使用rtkquery进行api接口的请求
  3. 如何使用axios替换rtkquery的默认fetch请求
  4. 如何使用rtkquery发送get请求和post请求
  5. 如何在组件mouted的时候,不立即执行rtkquery返回的hooks函数

什么是redux-toolkit

Redux Toolkit是一个官方推荐的用于简化Redux开发的工具集。它提供了一组工具和API,可以帮助开发者更快速、更简洁地编写Redux代码。Redux Toolkit包含了一些常用的Redux模块,如createSlice、createAsyncThunk和createReducer等,这些模块可以减少样板代码的编写,提高开发效率。此外,Redux Toolkit还集成了Immer库,使得在Redux中使用不可变数据更加方便。总之,Redux Toolkit旨在简化Redux的使用,提供更好的开发体验。

什么是rtkquery

Redux Toolkit Query (RTK Query) 是 Redux Toolkit 的一个插件,用于处理数据获取和管理。它提供了一种简化和标准化数据获取的方式,使得在 Redux 应用中进行数据请求变得更加容易和高效。

RTK Query 的主要特点包括:

  1. 自动化的数据获取和缓存管理:RTK Query 可以自动处理数据的获取、缓存和更新,无需手动编写大量的异步 action 和 reducer。
  2. 强大的数据查询和变换能力:RTK Query 提供了丰富的查询和变换操作,可以轻松地对数据进行过滤、排序、分页等操作。
  3. 内置的网络层和错误处理:RTK Query 内置了网络请求层,可以处理网络请求的发送和响应,同时也提供了错误处理和重试机制。
  4. 集成了 Redux Toolkit:RTK Query 是 Redux Toolkit 的一部分,可以与 Redux 的其他功能无缝集成,如状态管理、中间件等。

总之,RTK Query 是一个强大且易于使用的工具,可以大大简化 Redux 应用中的数据获取和管理过程,提高开发效率和代码质量。

如何在RTK Query里边使用axios

RTK Query默认是使用的fetch作为请求的,但是我们也可以使用我们自己封装的axios作为默认请求方法。

比如首先在项目的utils中新建axios.ts,对于axios进行简单的二次封装

import axios from 'axios';
import { useNavigate } from 'react-router-dom';
// 用于显示错误信息

const request = axios.create({
  baseURL: import.meta.env.VITE_REACT_APP_API_BASE_URL, // api的base_url
  timeout: 5000, // 请求超时时间
});

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    // 在这里可以做一些请求前的操作
    const myToken = localStorage.getItem('token');
    if (myToken) {
      config.headers['Token'] = 'Bearer ' + myToken;
    }
    return config;
  },
  (error) => {
    // 请求错误处理
    // for debug
    Promise.reject(error);
  }
);

// 响应拦截器
request.interceptors.response.use(
  (res) => {
    return res.data;
  },
  (error) => {
    if (error?.response?.status === 401) {
      const navigator = useNavigate();
      navigator('/login');
    }
    // console.log(999, error.message);
    // message.error(error.message);
    return Promise.reject(error);
  }
);

export default request;

然后在项目新建文件加services,在services下面新建api.ts,在api.ts里边新建axiosBaseQuery函数,使用axiosBaseQuery作为rtkQuery的默认请求方法。 代码如下:

import { createApi } from '@reduxjs/toolkit/query/react';
import type { BaseQueryFn } from '@reduxjs/toolkit/query/react';
import type { AxiosRequestConfig, AxiosError } from 'axios';
import request from '@/utils/axios';

// 定义自己的axios请求函数,代替rtkquery提供的fetch
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 {
      // Axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com/';
      const result = await request({
        url: baseUrl + url,
        method,
        data,
        params,
      });
      // 必须return object,否则rtkquery不认识
      return { data: result.data };
    } catch (axiosError) {
      const err = axiosError as AxiosError;
      return {
        error: {
          status: err.response?.status,
          data: err.response?.data || err.message,
        },
      };
    }
  };

const apiService = createApi({
  reducerPath: 'api',
  baseQuery: axiosBaseQuery(),
  endpoints: () => ({}),
});
export default apiService;

然后把函数注册到store.ts里边

import { configureStore } from '@reduxjs/toolkit';
import apiService from '@/services/api';
import searchReducer from '@/features/search';

export const store = configureStore({
  reducer: {
    searchSlice: searchReducer,
    // 引入文件进行注册
    [apiService.reducerPath]: apiService.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(apiService.middleware),
});

// 根节点的rootState类型
export type RootState = ReturnType<typeof store.getState>;
// 根节点的AppDispatch类型
export type AppDispatch = typeof store.dispatch;

然后在react 应用的顶层挂载上去

import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';

import 'antd/dist/reset.css';
import './app.css';
import './iconfont.js';
import routeres from './routes/index';
import { store } from './store/store';
import { Provider } from 'react-redux';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
    <Provider store={store}>
      <RouterProvider router={routeres}></RouterProvider>
    </Provider>
  </React.StrictMode>
);

当做完上面这些以后,基本的前置操作都已经完成了,下面定义一些状态

import apiService from './api';

export interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}

export interface CreatePostDto {
  title: string;
  body: string;
}

export const postService = apiService.injectEndpoints({
  endpoints: (builder) => ({
    // 请求文件
    getPosts: builder.query<Post[], null>({
      query: () => ({ method: 'GET', url: '/api/posts' }),
    }),
    // 修改文件
    createPost: builder.mutation<Post, CreatePostDto>({
      query: (data) => ({
        method: 'POST',
        url: '/api/posts1',
        // We pass static userId to simplify this part
        data: { userId: 1, ...data },
      }),
    }),
  }),
});

// 自动生成自定义hooks, useGetPostQuery 是去获取,在组件挂载的时候,立即执行,
// useCreatePostMutation 会返回执行的函数,手动触发
// useLazyGetPostsQuery 是不在组件挂载的时候执行,需要手动执行
export const { useGetPostsQuery, useCreatePostMutation, useLazyGetPostsQuery } =
  postService;

然后就可以在组件进行使用了

比如如下方式:

const {
    data: posts,
    isLoading,
    error,
    refetch,
  } = useGetPokemonByNameQuery('ssd');
  console.log(useAppSelector((state) => state));
  // const [createPosts, { isLoading }] = useGetPokemonByNameQuery();
  // useEffect(() => {
  //   createPosts({
  //     title: '11',
  //     body: '1223',
  //   });
  // }, []);

通过查看浏览器,可以看到现在已经可以使用了

企业级项目如何使用redux-toolkit 和RTKQuery

如何在组件mounted的时候,不立即触发函数

这里有两种方法

  1. 一种是使用一种状态进行Conditional Fetching
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'
import type { PokemonName } from './pokemon.data'

export const Pokemon = ({ name }: { name: PokemonName }) => {
  const [skip, setSkip] = React.useState(true)
  const { data, error, isLoading, isUninitialized } = useGetPokemonByNameQuery(
    name,
    {
      skip,
    }
  )

  const SkipToggle = () => (
    <button onClick={() => setSkip((prev) => !prev)}>
      Toggle Skip ({String(skip)})
    </button>
  )

  return (
    <>
      {error ? (
        <>Oh no, there was an error</>
      ) : isUninitialized ? (
        <div>
          {name} - Currently skipped - <SkipToggle />
        </div>
      ) : isLoading ? (
        <>loading...</>
      ) : data ? (
        <>
          <div>
            <h3>{data.species.name}</h3>
            <img src={data.sprites.front_shiny} alt={data.species.name} />
          </div>
          <SkipToggle />
        </>
      ) : null}
    </>
  )
}

这里最关键的就是skip,通过控制skip的状态来控制是否执行

  1. 使用上面提到的useLazyGetPostsQuery方式, useLazyGetPostsQuery方式会返回一个控制函数,当你需要的时候就可以调用了,这样就不会在组件刚开始挂载的时候进行执行了。