如何向Redux Thunk传递多个额外参数
下方代码是Redux Thunk单额外参数注入的官方示例写法:
import { configureStore } from '@reduxjs/toolkit' import rootReducer from './reducer' import { myCustomApiService } from './api' const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: { extraArgument: myCustomApiService, }, serializableCheck: false, }), })
针对需要传入多个依赖的场景,除了将所有依赖整合为单个对象传入extraArgument的官方推荐方案外,还有以下几种可行实现:
方案1:依赖收拢为单对象传入extraArgument(官方推荐)
这是Redux Toolkit官方明确推荐的多依赖传递方式,只需要把所有需要注入的依赖统一放到一个普通对象中传入即可,thunk内部直接解构使用:
// 统一收拢全局依赖 const dependencies = { api: myCustomApiService, logger: myCustomLogger, storage: myLocalStorageWrapper, appConfig: runtimeConfig } const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: { extraArgument: dependencies, }, serializableCheck: false, }), }) // thunk中使用 const fetchUser = (userId) => async (dispatch, getState, { api, logger }) => { logger.info('拉取用户信息') const data = await api.getUser(userId) dispatch(userLoaded(data)) }
这种方案几乎没有额外维护成本,TypeScript类型扩展简单,和RTK所有内置特性兼容,无特殊需求优先选择该方案。
方案2:替换默认thunk中间件,自定义多参数注入逻辑
如果不想把参数包成对象,可以关闭RTK默认内置的thunk中间件,自己实现thunk逻辑,按顺序传入多个参数:
import { configureStore } from '@reduxjs/toolkit' import rootReducer from './reducer' import { myCustomApiService } from './api' import { myLogger } from './utils/logger' import { storage } from './utils/storage' // 自定义thunk中间件 const multiArgThunk = ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { // 按顺序传入多个依赖,无需包裹为对象 return action(dispatch, getState, myCustomApiService, myLogger, storage) } return next(action) } const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ thunk: false, // 关闭默认thunk serializableCheck: false, }).concat(multiArgThunk), }) // thunk中按顺序接收参数即可 const fetchUser = (userId) => async (dispatch, getState, api, logger, storage) => { const cached = storage.get(`user_${userId}`) if (cached) return dispatch(userLoaded(cached)) logger.info('请求用户接口') const data = await api.getUser(userId) storage.set(`user_${userId}`, data) dispatch(userLoaded(data)) }
这个方案的缺点是需要自己维护thunk的核心逻辑,如果后续redux-thunk新增特性(比如abort信号支持)需要手动同步代码,参数顺序一旦调整需要修改所有thunk的参数接收逻辑,维护成本较高,仅适合有特殊参数顺序要求的历史项目兼容。
方案3:thunk工厂函数通过闭包预绑定依赖
不需要修改store的中间件配置,编写thunk时用高阶函数提前把依赖注入,业务使用时直接调用工厂方法生成绑定好依赖的thunk即可:
// 定义thunk工厂,接收依赖后返回真正可dispatch的thunk export const makeFetchUserThunk = (api, logger) => (userId) => async (dispatch, getState) => { logger.info('拉取用户信息') const data = await api.getUser(userId) dispatch(userLoaded(data)) } // 组件中使用 import { useDispatch } from 'react-redux' import { myCustomApiService } from './api' import { myLogger } from './utils/logger' import { makeFetchUserThunk } from './thunks/user' function UserProfile() { const dispatch = useDispatch() // 生成绑定好依赖的thunk const fetchUser = makeFetchUserThunk(myCustomApiService, myLogger) return <button onClick={() => dispatch(fetchUser(1))}>加载用户</button> }
这个方案的优点是依赖完全显式传入,单元测试时可以轻松传入mock依赖,不需要包装整个store;缺点是全局通用依赖每次使用都要手动传入,重复代码多,适合依赖不是全局通用、需要按场景动态传入的场景。
注意:如果项目使用TypeScript,方案1的类型声明扩展最简单,只需要扩展
ThunkExtraArgument的类型即可全局获得正确的类型提示,其余两个方案都需要手动编写复杂的类型声明覆盖默认thunk类型。
内容的提问来源于stack exchange,提问作者Mohamed Hossam Eldine

