你不了解的 @reduxjs/toolkit 中的createApi

1,162 阅读3分钟

背景

其实网上关于@reduxjs/toolkit的介绍很多了,但是我发现很少有关于createApi这个方法介绍的,这个功能却又特别强大,于是写下这篇文章

正文开始

// 先体验一下不用createApi时如何处理异步请求

// 第一步 创建store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import globalReducer from "./globalReducer";

const store = configureStore({
  reducer: {
    globalReducer,
  },
});

export default store;

// 这里是处理 useDispatch 和 useSelector没有ts类型提示
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

// 第二步 新建 store/globalReducer.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

// 初始化数据类型
type Props = {
  userInfo: {
    routerList: string[];
    name: string;
    loading: boolean;
  };
};

// 初始化数据
const initialState: Props = {
  userInfo: {
    routerList: [],
    name: "",
    loading: true,
  },
};

// 模拟一个接口 异步请求
const getUserInfo = (params: any): Promise<any> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        routes: ["home", "detail", "list", "404", "home/detail"],
        userName: "jianjian",
      });
    }, 1000);
  });
};

// 通过 createAsyncThunk 创建一个promise的异步方法
export const fetchUserInfo = createAsyncThunk<any>(
  "globalReducer/getUsreInfo",
  async (params, thunkAPI) => {
    const data = await getRoutes(params);
    return data;
  }
);

// 创建一个切片
const globalReducer = createSlice({
  name: "globalReducer",
  initialState,
  reducers: {
    add() {
      console.log(111);
    }
  },
  // 异步请求修改数据的方法(promise)
  extraReducers: (builder) => {
    builder.addCase(fetchUserInfo.pending, (state) => {
      state.userInfo.loading = true;
    });
    builder.addCase(fetchUserInfo.fulfilled, (state, action) => {
      state.userInfo.loading = false;
      state.userInfo.routerList = action.payload.routes;
      state.userInfo.name = action.payload.name;
    });
  },
});

const { actions, reducer } = globalReducer
export default globalReducer.reducer;

// 第三步 由于使用 useDispatch 和 useSelector 没有ts类型提示,我们重写一些 
// 新建 hooks/index.ts
import { AppDispatch, RootState } from "../store";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";

/** @description 解决 useDispatch 和 useSelector没有ts类型提示 */
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// 第四步 组件中使用 Demo.tsx
import { useAppSelector } from "../hooks";
const Demo = () => {
  const {
    userInfo: { routerList, loading },
  } = useAppSelector((state) => state.globalReducer);
  return <>{loading ? <div>loading...</div> : <div>403......</div>}</>;
};
export default Demo;
我们看到以上处理异步情况需要做的工作
1 通过 createAsyncThunk 创建一个promise的reducer方法
2 在extraReducers中处理异步请求的情况 (pending, fulfilled, reject)
3 根据不同的情况修改initialState像loading 请求的数据 错误信息等等...
4 然后在组件中 去dispatch我们通过createAsyncThunk创建的任务
5 然后在组件中通过 useSelectore 去获取我们获取的数据

以上很多工作还是需要我们手动去处理和判断,
而且如果接口多的情况下很多工作都是重复的(最基本的像loading, 数据,请求状况),
那我们能不能把这些重复的工作省略掉呢? createApi就可以!!!

通过createApi去改造我们上面的异步请求

// 第一步 新建 store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import globalReducer from "./globalReducer";

// createApi文档 https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#axios-basequery

const store = configureStore({
  reducer: {
    [globalReducer.reducerPath]: globalReducer.reducer
  },
});

export default store;

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

// 第二步 新建 store/globalReducer.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

const globalReducer = createApi({
   // 这个reducerPath相当于我们 createSlice配置中的name
   reducerPath: 'globalReducer',
   // 配置一些异步请求 fetchBaseQuery是库内置的基于fetch封装的请求方法
   baseQuery: fetchBaseQuery({
    baseUrl: "http://localhost:8080",
    prepareHeaders: (headers, { getState }) => {
      const token = '你的token';
      if (token) {
        headers.set("authorization", `Bearer ${token}`);
      }
      return headers;
    },
  }),
  // 配置你的异步方法
   endpoints: (builder) => {
     return {
       // 定义一个接口
       //从参数生成查询参数 转变响应并且缓存
       //React entry point 会自动根据endpoints生成hooks
       getTodos: builder.query({query: (id) => ({
          method: 'GET',
          url: `/todos/detail/${id}`,
          params: {
             name: 'xxxx'
          }
       })}),
     }
   }
 })

export default globalReducer;

// 第三步 组件中使用 Demo.tsx 
import globalReducer from '../globalReducer'

const Demo = () => {
  // useGetTodosQuery 是根据 第二步中的 endpoints下的 getTodos方法名自动生成的hook
  const { isLoading, data, isError, isSuccess } = globalReducer.useGetTodosQuery(1)
  return <>{isLoading ? <div>loading...</div> : <div>403......</div>}</>;
1 通过createApi 以上三步骤就完成了一个异步请求
2 我们可以对比一下
3 useGetTodosQuery 是根据 第二步中的 endpoints下的 getTodos方法名自动生成的hook,
4 如果我们方法名叫做 getAdd, 那么讲自生成一个useGetAddQuery的hook
5 (明白了嘛,自动生成的,你也可以把useGetTodosQuery写成todoApi.endpoints.getTodos)
6 globalReducer.useGetTodosQuery(1) 表示我们调用 getTodos这个接口并且传入参数1
7 返回值中,已经帮我们处理好了loading,数据,错误等等 类似ahooks中的useRequest
7 第二步的请求是toolkit内置的fetchBaseQuery方法,有时候我们想用自己的或者基于axios封装的怎么办呢?往下看

createApi采用axios封装请求方法

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 globalReducer = createApi({
  reducerPath: 'globalReducer',
  baseQuery: axiosBaseQuery({ baseUrl: 'http://localhost:8080' }),
  endpoints: (builder) => {
    return {
      //从参数生成查询参数 转变响应并且缓存
      getTodos: builder.query({query: (id) => ({
         method: 'POST',
         url: `/todos/detail/${id}`,
         params: {
            name: 'jiajian'
         }
      })}),
       getList: builder.query({
         query: () => `/todos/list`
       }),
    }
  }
})
over,希望大家有所收获~