Redux 中间件

96 阅读4分钟

Redux 中间件

Redux 中间件允许你在派发一个 action 到达 reducer 之前,对该 action 进行一些额外的处理。中间件提供了一个拦截 action 的机制,你可以在中间件中执行一些额外的逻辑,例如记录日志、处理异步操作、触发其他 action 等。它们是 Redux 强大和灵活的特性之一。

redux数据流程大致是:

image.png

redux增加中间件处理副作用后的数据流大致如下:

image.png

一 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,如果组件需要知道请求是否失败,需要使用.unwraporunwrapResult并相应地处理重新抛出的错误。

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处理错误。

三种Redux中间件处理数据的区别