Redux Toolkit 调用 API 的四种方式

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redux Toolkit 调用 API 的四种方式相关的知识,希望对你有一定的参考价值。

Redux Toolkit 调用 API 的四种方式

上篇笔记写的比较乱,加上这回又学了点新的东西,所以重新整合一下。

本地 API 用的是 json-server,端口设置在 3005,数据包含:


  "users": [
    
      "id": 1,
      "name": "Myra"
    ,
    
      "name": "Pete Bernhard",
      "id": 9
    ,
    
      "name": "Bert Block",
      "id": 10
    ,
    
      "name": "Rachael Bayer",
      "id": 11
    
  ]

基础写法

这种方法利用了 Redux Toolkit 现在会将同名的 reducers 与 actions 进行归并,对比使用 redux 不需要额外进行 actions 的声明,因此减少了代码量。

副作用的触发和逻辑放在 Component 中,优化方式可以通过写对应的 hooks 降低代码的重复率,或者使用 thunk。

  • slice

    与原生的 Redux 相比,RTK 新定义了一个 slice,将 reducers、actions、state 放入了 slice 中进行管理:

    import  createSlice  from '@reduxjs/toolkit';
    
    export const userSliceNative = createSlice(
      name: 'users',
      initialState: 
        data: [],
        isLoading: false,
        error: null,
      ,
      reducers: 
        fetchingData(state) 
          state.isLoading = true;
        ,
        fatchingDataCompleted(state) 
          state.isLoading = false;
        ,
        setData(state, action) 
          state.data = action.payload;
        ,
        setError(state, action) 
          state.data = action.error;
        ,
      ,
    );
    
  • store:

    store 的整合也较为简单,这里将 actions 放入了 store 中,这样其他组件可以直接从 store 中进行导入,非必需。

    import  configureStore  from '@reduxjs/toolkit';
    import logger from 'redux-logger';
    import  userSliceNative  from './slices/userSliceNative';
    
    export const store = configureStore(
      reducer: 
        userNative: userSliceNative.reducer,
      ,
    );
    
    export const userNativeActions = userSliceNative.actions;
    
  • react component:

    Again,如果决定用这种写法了,useEffect 中的代码可以通过编写一个 custom hooks 进行一定程度上的优化,减少代码重复率。

    import axios from 'axios';
    import React,  useEffect  from 'react';
    import  useDispatch, useSelector  from 'react-redux';
    import  userNativeActions  from '../store';
    
    const UserNative = () => 
      const  data, isLoading, error  = useSelector((state) => state.userNative);
      const dispatch = useDispatch();
    
      useEffect(() => 
        dispatch(userNativeActions.fetchingData());
        axios
          .get('http://localhost:3005/users')
          .then((res) => 
            dispatch(userNativeActions.setData(res.data));
          )
          .catch((err) => dispatch(userNativeActions.setError(err)))
          .finally(dispatch(userNativeActions.fatchingDataCompleted()));
      , []);
    
      let content;
    
      if (isLoading) 
        content = 'User is loading';
       else if (error) 
        content = 'Load user fail';
       else 
        content = data.map((user) => <div key=user.id>user.name</div>);
      
    
      return <div>content</div>;
    ;
    
    export default UserNative;
    

thunk 实现

这里就是写了一个额外的 action creator,RTK 现在原生就支持 thunk 实现。

store 的配置和第一种写法一样,就不重复 cv 了。

  • slice:

    import axios from 'axios';
    // import from central store
    import  userNativeActions  from '..';
    
    import  createSlice  from '@reduxjs/toolkit';
    
    export const userSliceNative = createSlice(
      name: 'users',
      initialState: 
        data: [],
        isLoading: false,
        error: null,
      ,
      reducers: 
        fetchingData(state) 
          state.isLoading = true;
        ,
        fatchingDataCompleted(state) 
          state.isLoading = false;
        ,
        setData(state, action) 
          state.data = action.payload;
        ,
        setError(state, action) 
          state.data = action.error;
        ,
      ,
    );
    
    export const fetchUsersThunk = () => 
      return (dispatch) => 
        dispatch(userNativeActions.fetchingData());
    
        axios
          .get('http://localhost:3005/users')
          .then((res) => 
            dispatch(userNativeActions.setData(res.data));
          )
          .catch((err) => dispatch(userNativeActions.setError(err)))
          .finally(dispatch(userNativeActions.fatchingDataCompleted()));
      ;
    ;
    
  • component:

    import React,  useEffect  from 'react';
    import  useDispatch, useSelector  from 'react-redux';
    import  fetchUsersThunk  from '../store/thunks/fetchUsersThunk';
    
    const UserThunk = () => 
      const  data, isLoading, error  = useSelector((state) => state.userNative);
      const dispatch = useDispatch();
    
      useEffect(() => 
        dispatch(fetchUsersThunk());
      , []);
    
      let content;
    
      if (isLoading) 
        content = 'User is loading';
       else if (error) 
        content = 'Load user fail';
       else 
        content = data.map((user) => <div key=user.id>user.name</div>);
      
    
      return <div>content</div>;
    ;
    
    export default UserThunk;
    

asyncThunk

asyncThunk 是 RTK 内部提供的一个对异步 thunk 的处理方式,优点在于 Promise 的状态是 RTK 内部进行管理的,不需要用户手动实现。

  • slice:

    与完全靠用户手写一个 custom action creator,状态管理需要用户在 custom action creator 中判断并触发不太一样的一点就是,这里的 custom action creator 非常短,短到只需要返回调用成功时,reducer 中需要处理的结果。至于异步调用的 pending/fulfilled/rejected,这点基本由 RTK 进行管理。

    在 slice 中的 extraReducers 中,只需要根据 RTK 管理的状态更新对应的数据即可。

    import  createSlice  from '@reduxjs/toolkit';
    
    const userSlice = createSlice(
      name: 'users',
      initialState: 
        data: [],
        isLoading: false,
        error: null,
      ,
      extraReducers(builder) 
        // retrieve
        builder.addCase(fetchUsers.pending, (state, action) => 
          state.isLoading = true;
        );
        builder.addCase(fetchUsers.fulfilled, (state, action) => 
          state.isLoading = false;
          state.data = action.payload;
        );
        builder.addCase(fetchUsers.rejected, (state, action) => 
          state.isLoading = false;
          state.error = action.error;
        );
      ,
    );
    
    export const fetchUsers = createAsyncThunk('users/fetch', async () => 
      const response = await axios.get('http://localhost:3005/users');
    
      return response.data;
    );
    
    export const usersReducer = userSlice.reducer;
    
  • store:

    store 的挂载方式也与之前的一样

    import  configureStore  from '@reduxjs/toolkit';
    import  usersReducer  from './slices/usersSlice';
    import logger from 'redux-logger';
    import  userSliceNative  from './slices/userSliceNative';
    
    export const store = configureStore(
      reducer: 
        userNative: userSliceNative.reducer,
        users: usersReducer,
      ,
      middleware: (getDefaultMiddleware) => 
        return getDefaultMiddleware().concat(logger);
      ,
    );
    
  • component:

    关于 dispatch、useState 这块代码也可以另外封装一个 custom hooks 去进行实现。使用 asyncThunk 的封装应该相对而言更加容易,毕竟 Promise 的三个状态都是有 RTK 进行定义和管理的,基本上可以保证一致性。

    function UsersList() 
      const [isLoadingUsers, setIsLoadingUsers] = useState(false);
      const [loadingUsersError, setLoadingUsersError] = useState(null);
      const dispatch = useDispatch();
      const  data  = useSelector((state) => state.users);
    
      useEffect(() => 
        setIsLoadingUsers(true);
        dispatch(fetchUsers())
          .unwrap()
          .catch((err) => setLoadingUsersError(err))
          .finally(() => setIsLoadingUsers(false));
      , []);
    
      let content;
    
      if (isLoadingUsers) 
        content = 'User is loading';
       else if (loadingUsersError) 
        content = 'Load user fail';
       else 
        content = data.map((user) => <div key=user.id>user.name</div>);
      
    
      return <div>content</div>;
    
    

对比 与前两者:

前两者对于 Promise 处理的 reducers 还是属于手动的,使用 asyncThunk 则显得更加的规范化,不过保存的数据格式倒是一致的:

Redux Toolkit Query

RTKQ 有点对标 React Query,都是对 API 进行 cache 的解决方案,基础的实现方法如下:

  • slice/api:

    import  createApi, fetchBaseQuery  from '@reduxjs/toolkit/dist/query/react';
    
    const userApi = createApi(
      reducerPath: 'users',
      // pre-configured fetch
      baseQuery: fetchBaseQuery(
        baseUrl: 'http://localhost:3005',
      ),
      endpoints(builder) 
        return 
          fetchUsers: builder.query(
            query: (user) => 
              return 
                url: '/users',
                method: 'GET',
              ;
            ,
          ),
        ;
      ,
    );
    
    export const  useFetchUsersQuery  = userApi;
    export  userApi ;
    
  • store:

    import  configureStore  from '@reduxjs/toolkit';
    import logger from 'redux-logger';
    import  userApi  from './apis/userApi';
    
    export const store = configureStore(
      reducer: 
        [userApi.reducerPath]: userApi.reducer,
      ,
      middleware: (getDefaultMiddleware) => 
        return getDefaultMiddleware().concat(userApi.middleware).concat(logger);
      ,
    );
    
  • component:

    import React from 'react';
    import  useFetchUsersQuery  from '../store/apis/userApi';
    
    const UserRTK = () => 
      const  data, isFetching, error  = useFetchUsersQuery();
    
      let content;
    
      if (isFetching) 
        content = 'User is loading';
       else if (error) 
        content = 'Load user fail';
       else 
        content = data.map((user) => <div key=user.id>user.name</div>);
      
    
      return <div>content</div>;
    ;
    
    export default UserRTK;
    

RTKQ 内部的管理流程大致如下:

middleware 显示 register 对应的 Query,接着就像 Thunk2 一样,内部对 pending/fulfilled/rejected 的状态进行管理,这里也能看到一个 internalSubscriptions 的存在。

这里只是一个简单的 Fetch,并不包含 CRUD,不过需要注意的是,拉数据是调用 builder.query 进行创建,而其他会对数据库产生影响的作用则使用 builder.mutate 进行创建。

除此之外,如果当前的 API 没有被触发,那么对应的数据并不会存在(因为没有被 cache),换言之如果我还有一个 product 的 slice/api,只是在用户页面没有调用 product 的 RTKQ,那么 product 就不会存在于 state 中。

RTKQ 只所以能够被称之为是一个对 query 的解决方案,也是因为通过一些配置就能够实现一些比较麻烦的功能:

而且 RTKQ 内部还有一个 tagging system,RTKQ 在 Fetch 阶段可以提供一个 tag,其内部会对这个 tag 进行缓存,在 mutation 阶段用户可以通过 invalidatesTags 对 tag 去无效华内部的 cache,当 RTKQ 察觉到 tag 的变化后,那么就会重新调用 query 去拉取最新的数据。

下一步大概就是研究一下 RTKQ 这个 tagging system 了,毕竟我还挺需要手动更新数据而不是让 RTKQ 去重新拉取数据(这个也是可以实现的)。

以上是关于Redux Toolkit 调用 API 的四种方式的主要内容,如果未能解决你的问题,请参考以下文章

Redux Toolkit 调用 API 的四种方式

Redux-Toolkit = 无法使用 useSelector 显示 API 响应数据

如何在 Redux-toolkit 中使用两个不同的切片?

如何使用 redux-toolkit 动态更改基本 URL?

使用 Redux Toolkit 异步 REST API 模式时如何捕获 HTTP 4xx 错误?

Reducer 状态没有被新对象更新 [redux, redux-toolkit, normalize]