redux-toolkit 在 react 中的用法(续)

更多分享内容可访问我的个人博客

https://www.niuiic.top/

rtk-query

rtk-query 是进行网络请求的一个高级工具,下面介绍一下使用流程。

首先是新建一个基础 api。

// apis/base.ts

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export const baseApi = createApi({
  // 根据url自动选择http或者https协议
  // 如果是移动端应用,由于模拟器中的网络与本地不一致,即使服务器在本地也不能使用127.0.0.1,必须用局域网地址
  baseQuery: fetchBaseQuery({ baseUrl: "http://192.168.31.228:8000/v1/" }), // 发送请求的目的地址的前半部分
  reducerPath: "baseApi", // 告诉redux-toolkit我们想把从这个api获取的数据放到store的什么位置
  endpoints: () => ({}), // endpoints中放的是各种请求相关的函数,这里暂时为空,后续写具体的Api时再补充。
});

如果你没有现成的服务端程序,可以在 https://pokeapi.co/ 进行测试。

然后是写一个具体的 api,填充 endpoints。

// apis/user.ts

import { baseApi } from "../base";

// 请求返回值的类型
// 这里不需要严格对应,举个例子
// 假设真实返回值的类型比下面还多个time字段,或者说并没有data字段,下面定义的类型也是不会报错的
// 甚至返回的是一个json文件,且定义的返回值类型为string也是可以的,最终可以通过`.`操作符获取其中的字段
interface User {
  data: string;
}

// 在baseApi的基础上创建userApi
const userApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    // 根据id去查询
    // query用于检索数据,并可以向缓存提供标签
    // 第一个参数是返回值的类型,第二个参数是传递给后端的数据类型(这里传递的是id,为number类型)
    getUserById: builder.query<User, number>({
      // 这里参数的类型必须和上面定义的一致
      query: (id: number) => ({
        // 请求地址的后半部分
        url: `/user/${id}`,
        // 请求的方法
        method: "get",
      }),
    }),
    // 根据id删除用户
    // 对于改变服务器上的数据或可能会使缓存无效的任何内容应当使用mutation
    deleteUserById: builder.mutation({
      query: (id: number) => ({
        url: `/user/${id}`,
        method: "delete",
      }),
    }),
  }),
  overrideExisting: false,
});

// 导出可在函数式组件使用的hooks,它是基于定义的endpoints自动生成的
// 这个的名称是固定的,就是use加上前面定义的名字再加上query或者mutation
export const { useGetUserByIdQuery, useDeleteUserByIdMutation } = userApi;

然后就是将定义好的 api 与 store 联系起来。

// stores/Welcome.ts

import { configureStore } from "@reduxjs/toolkit";
import loginReducer from "../components/welcome/LoginRedux";
import { baseApi } from "../apis/base";
import { setupListeners } from "@reduxjs/toolkit/dist/query";

// 中间件集合
const middlewareHandler = (getDefaultMiddleware: any) => {
  const middlewareList = [...getDefaultMiddleware()];
  // 把api自动生成的中间件加进去
  middlewareList.push(baseApi.middleware);
  return middlewareList;
};

export const welcomeStore = configureStore({
  reducer: {
    // login是Welcome页面的一个模块
    login: loginReducer,
    // 把api自动生成的reducer加进入
    [baseApi.reducerPath]: baseApi.reducer,
  },
  middleware: (getDefaultMiddleware) => middlewareHandler(getDefaultMiddleware),
});

// 这里主要是监听焦点、网络通断、组件可视性变化和其他一些事件,如果没有这些情况,就不需要监听
setupListeners(welcomeStore.dispatch);

export type WelcomeState = ReturnType<typeof welcomeStore.getState>;
export type WelcomeDispatch = typeof welcomeStore.dispatch;

现在已经全部配置完成,可以使用了。

// components/Login.tsx

import { TextInput, Button, View, Text } from "react-native";
import { useGetUserByIdQuery } from "../../apis/welcome/user";

export function Login() {
  const { data, isLoading, isError } = useGetUserByIdQuery(1);

  if (isLoading) {
    return (
      
        loading
      
    );
  }

  if (isError) {
    return (
      
        Something went wrong
      
    );
  }

  return (
    
      The response is {data}
    
  );
}

如果要进行轮询,那也很简单。前有export const { useGetUserByIdQuery, useDeleteUserByIdMutation } = userApi;。只需在使用时传入轮询间隔即可。比如像下面这样。

const { data, isLoading, isError, refetch } = useGetUserByIdQuery(1, {
  pollingInterval: 3000,
});

由事件触发请求

export function Login() {
  const { data, isLoading, isError } = useGetUserByIdQuery(1);
}

从前面的例子中可以看到,发出请求的这些函数只能在函数式组件内部使用,也就是说,请求函数永远在组件被渲染之前调用,想要直接将其作为事件的处理函数是不可能的。如果需求是当按钮按下时发出才请求,就必须要进行进一步处理。

解决该问题的方案有三种,一种是使用lazy版本的函数,比如useLazyGetUserByIdQuery,一种是使用选择性调用特性,还有一种是使用mutation

第一种可能写的比较多,这里只介绍后两种。下面是代码片段。

import { useState } from "react";
export function Login() {
  const [skip, setSkip] = useState(true);
  // 下面的useVerifyUserQuery就相当于前面的useGetUserByIdQuery,与上面的定义方式没有区别。
  // userState是useVerifyUserQuery定义时的参数,等同于useGetUserByIdQuery的id:number。
  // {skip}是传入的第二个参数,该参数不需要定义。
  // 这些配置使得该函数不会在一开始就执行。
  const { data, error, isLoading, isUninitialized } =
    Hooks.user.useVerifyUserQuery(userState, { skip });

  // 然后定义一个按钮触发请求函数。
  // 至此已经可以实现需求。除了这里的改动之外,其他地方都没有变。
  const LoginButton = () => (
    <Button title="登陆" onPress={() => setSkip((prev) => !prev)} />
  );

  return (
    ……
  );
}

那么假设现在有两个按钮,每个按钮对应不同的请求,其余和之前相同,之前的写法还能用吗。显然是不行的,因为skip就一个,按一个按钮给它改了,两个都触发了。所以,还需要改进。

再稍微看一下上面的代码,skip就只是一个boolean值,那么问题已经解决了。用createSlice再建一个 Slice,其 state 的类型就假设为number。然后可以在页面中获取该 state 的值。现在用{skip : !(state == 1)}代替{skip},另一个写{skip : !(state == 2)}。按钮响应事件改成修改state的值就行,后面的就不用说了。这样不管是想要用一个按钮触发几个请求,不管页面上有多少个按钮想触发请求都是可以实现的。当然1, 2这种过于低级,应当自行包装一下。

第三种方案是mutation,以下是代码片段。

export const UserApi = BaseApi.injectEndpoints({
  endpoints: (builder) => ({
    verifyUser: builder.mutation<boolean, User>({
      query: (user: User) => ({
        url: `/user/${user.name}:${user.password}`,
        method: "get",
      }),
      // 第一个参数是传入的User
      // 第二个参数是MutationLifecycleApi的解构
      async onQueryStarted(
        arg,
        { dispatch, getState, queryFulfilled, requestId, extra, getCacheEntry }
      ) {
        console.log("onQueryStarted");
      },
      // 第一个参数同上
      // 第二个参数是MutationCacheLifecycleApi的解构
      async onCacheEntryAdded(
        arg,
        {
          dispatch,
          getState,
          extra,
          requestId,
          cacheEntryRemoved,
          cacheDataLoaded,
          getCacheEntry,
        }
      ) {
        console.log("onCacheEntryAdded");
      },
    }),
  }),
  overrideExisting: false,
});

这里定义了一个mutation函数,其中有两个钩子函数,分别是onQueryStartedonCacheEntryAdded,看名称就知道是怎么回事。

const [verifyUser, result] = Hooks.user.useVerifyUserMutation();

const LoginButton = () => (
  <Button
    title="登陆"
    onPress={() => {
      verifyUser(userState);
    }}
  />
);

使用的时候也很简单,useVerifyUserMutation()会返回一个触发函数和一个result。触发函数可以在生命域内任何地方使用。result内含status, error, data

现在还有一个问题,如何在发出请求并且接收到数据之后再对数据进行处理。比如,请求返回了用户的信息,想把这个信息存到 store 中。在刚才的例子中

你可能感兴趣的:(react,react.js,redux-toolkit,rtk-query,axios)