Redux 中间件
Redux 中间件允许你在派发一个 action 到达 reducer 之前,对该 action 进行一些额外的处理。中间件提供了一个拦截 action 的机制,你可以在中间件中执行一些额外的逻辑,例如记录日志、处理异步操作、触发其他 action 等。它们是 Redux 强大和灵活的特性之一。
redux数据流程大致是:
redux增加中间件处理副作用后的数据流大致如下:
一 Redux-Saga
redux-saga使用了 ES6 的 Generator 语法,通过封装的各个api对副作用执行,暂停和取消。其实还是订阅发布者模式,从界面dispatch一个action对象,在saga里面监听到这个action type。然后在对应函数里做一些副作用操作,最后在dispatch一个action通知reducer更新store,从而更新界面。只是订阅的模式是通过generator的深度自动执行实现的,redux-saga实现了一个自动执行generator的函数。让异步的流程更易于读取,写入和测试。
redux-saga配置:
- const sagaMiddleware =createSagaMiddleware();
- 在通过applyMiddleware引入后,执行sagaMiddleware.run(rootSaga);
一些api用法:
- call: 一般用来生成一个Effect,发起api的请求之类的。
- put: 将处理完成的数据通过发送一个action到reducer,reducer再更新到store里。
- takeEvery: 匹配一个dispatch到store的action
import { put, call, takeEvery } from `redux-saga/effects`
function* fetchUser(action) {
try {
const {payload} = action
const user = yield call(Api, payload.user);
payload.callback(user) // 通过callback返回数据
yield put({type: "USER_FETCH_SUCCEEDED", user: user}); // 将结果发送给reducer
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga
// 页面调用
dispatch({type: 'USER_FETCH_REQUESTED', user: {name: 'test'}})
结果处理:
- 在saga里通过调用put api的方式,将结果dispatch给reducer保存到store中。
- 通过传入callback方式将结果返回给页面。
错误处理:
一般在saga里通过try/catch的方式捕获错误
并发请求:
通过all api一起发起api
import { all, call } from `redux-saga/effects`
function* mySaga() {
const [customers, products] = yield all([
call(fetchCustomers),
call(fetchProducts)
])
}
二 Redux-Thunk
redux-thunk是可以让dispatch传入一个funtion,在内部进行判断如果是function则可以在function里面自定义方法处理副作用。使用redux-thunk可以避免直接使用store去dispatch。
function increment() {
return {
type: 'INCREMENT'
}
};
// 异步action creator
function incrementAsync() {
return (dispatch) => {
setTimeout(() => {
dispatch(increment());
}, 1000);
}
}
store.dispatch(incrementAsync());
Store配置
- const store = createStore(rootReducer, applyMiddleware(thunk));
数据更新
- 通过dispatch参数将数据传给store
- 通过返回一个promise在页面接受参数
- 他的第二个参数????????????????????
并发请求
- 可以通过promise.all 同时发起多个api请求
Reduxjs/toolkit 中的 createAsyncThunk
API
createAsyncThunk
是对redux-thunk的增强,接受三个参数:字符串操作type
值、payloadCreator
回调和options
对象。
payloadCreator
是dispatch可以传入第一个参数arg,第二个thunkAPI包含通常传递给 Redux thunk 函数的所有参数的对象,以及其他选项:dispatch
, getState
等等
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
export const fetchTodos = createAsyncThunk('todos/fetchTodos', async (arg,thunkAPI) => {
const {dispatch, getState} = thunkAPI
const response = await client.get('/fakeApi/todos')
dispatch({type:'RECEIVE_DATA', data: response.data})
return response.todos
})
返回一个promise可在界面通过then
回调获取返回参数,也可以通过createSlice
的方式将数据存入Store
import { createSlice } from '@reduxjs/toolkit'
const todosSlice = createSlice({
...
extraReducers: builder => {
builder
.addCase(fetchTodos.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.entities = action.payload
state.status = 'idle'
})
}
})
错误处理
失败的请求或 thunk 中的错误将永远不会返回被reject的promise,如果组件需要知道请求是否失败,需要使用.unwrap
orunwrapResult
并相应地处理重新抛出的错误。
const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap()
const originalPromiseResult = unwrapResult(resultAction)
在createAsyncThunk
方法里一般还是通过try/catch的方式处理错误。
三 Reduxjs/toolkit Query
RTK Query重点关注数据获取与缓存逻辑,不用重新封装获取数据的api。
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { User } from './types'
export const userApi = createApi({
reducerPath: 'userApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://user/api/' }),
endpoints: (builder) => ({
getUserByName: builder.query<User, string>({
// name 是参数,生成传递给 fetch 的参数
query: (name) => `getList/${name}`,
}),
}),
})
// 自动生成的 React Hooks
export const { useGetUserByNameQuery } = userApi
Store配置
import { userApi } from '../features/api/userApi'
export default configureStore({
reducer: {
users: userApi.reducer, // 添加reducer
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(userApi.middleware) // 添加middleware
})
RTK Query 生成封装整个数据获取过程的 React hooks ,为组件提供 data
和 isFetching
等字段,并在组件挂载和卸载时管理缓存数据的生命周期
import { useGetUserByNameQuery } from './services/pokemon'
export const UserComponent = ({ name }: { name: string }) => {
const {
data,
error,
isLoading,
isFetching,
refetch,
} = useGetUserByNameQuery(name)
return (
<div style={{ float: 'left', textAlign: 'center' }}>
{isLoading ? (<>Loading...</>) :
<div>
{data.species.name}
<button onClick={refetch} disabled={isFetching}>
{isFetching ? 'Fetching...' : 'Refetch'}
</button>
</div>}
</div>
)
}
缓存策略
由于RTK Query的缓存机制,在向服务器发起更新数据请求成功后,从其他界面切换回来界面没有获取数据,然后获取更新数据方法:
- Query hooks 结果对象包含一个
refetch
函数,我们可以调用它来强制重新获取。 - 通过添加
tagTypes
利用缓存失效的方式重新获取数据
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/fakeApi' }),
tagTypes: ['Post'], // 添加 tagTypes
endpoints: builder => ({
getPosts: builder.query({
query: () => '/posts',
providesTags: ['Post'] // 获取数据api添加 providesTags
}),
addNewPost: builder.mutation({
query: initialPost => ({
url: '/posts',
method: 'POST',
body: initialPost
}),
invalidatesTags: ['Post'] // 更新数据api添加 invalidatesTags
})
})
})
数据更新
- 通过
createSlice
api写reducer方法向store里更新数据,或者通过extraReducers监听query返回状态向store里更新数据。 - 在页面通过调用apiSlice导出的hooks解析出data使用。
错误处理
- 根据hooks返回的error处理错误。