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 = 无法使用 useSelector 显示 API 响应数据
如何使用 redux-toolkit 动态更改基本 URL?