React+Redux Toolkit下处理带依赖的action creators及HATEOAS集成方案咨询
基于Redux Toolkit的HATEOAS流程优化方案
核心优化思路
你期望的在async thunk内部dispatch action是完全可行的,createAsyncThunk的回调参数除了getState之外,本身就提供了dispatch方法,完全可以在单个thunk内部完成依赖检查、缺失资源加载、目标资源请求的全流程,不需要把多个响应对象带回reducer处理。
具体优化实现
1 拆分全局链接拉取逻辑
把根链接的拉取逻辑拆成独立的thunk,请求成功后直接存入store,不需要向上层返回:
export const fetchRootLinksAsync = createAsyncThunk<APILinkResponse, string>( 'api/fetchRootLinks', async (rootUri) => { const res = await axios(rootUri, requestConfig); if (!res.data?._links) throw new Error('无法从根响应中解析链接'); return res.data; } );
在slice的extraReducers中新增对应的处理逻辑:
.addCase(fetchRootLinksAsync.fulfilled, (state, action) => { processLinks(action.payload._links, state); })
2 简化任务请求thunk
在任务请求thunk内部完成链接依赖检查,缺失时直接dispatch拉取根链接的thunk,等待完成后再发起任务请求:
export const requestTasksAsync = createAsyncThunk<APITasksResponse, { uri: string; pageOptions?: PageOptions; filterOptions?: FilterOptions; }>( 'api/tasks', async ({ uri, pageOptions, filterOptions }, { getState, dispatch }) => { const state = getState() as ApiState; const TASK_REL = 'http://acme.com/rel/tasks'; // 检查链接是否存在,不存在则先拉取根链接 if (!state.links || Object.keys(state.links).length === 0) { // unwrap()用于捕获内层thunk的错误 await dispatch(fetchRootLinksAsync(uri)).unwrap(); } // 从最新state中获取目标链接 const latestState = getState() as ApiState; const taskLink = latestState.links[TASK_REL]; if (!taskLink) throw new Error(`未找到对应链接关系:${TASK_REL}`); // 处理URI模板参数 let href = taskLink.href; if (taskLink.templated) { const templated = utpl(href); href = fillTemplateParameters(templated, pageOptions, filterOptions); } const res = await axios.get(href, requestConfig); return res.data; } );
3 简化reducer处理逻辑
不需要再处理多响应参数,直接处理任务接口返回的结果即可:
.addCase(requestTasksAsync.fulfilled, (state, action) => { const tasks = action.payload; processLinks(tasks._links, state); processPage(tasks.page, state); processTasks(tasks._embedded.tasks, state); state.status = StateTypes.SUCCESS; })
通用能力封装
可以把HATEOAS请求的通用逻辑封装为thunk创建器,后续新增其他资源请求时不需要重复写逻辑:
const createHateoasRequestThunk = <ReturnT, ParamsT = {}>( typePrefix: string, relName: string, defaultRootUri: string = 'http://localhost:8080/api' ) => { return createAsyncThunk<ReturnT, ParamsT & { pageOptions?: PageOptions; filterOptions?: FilterOptions; uri?: string; }>( typePrefix, async (params, { getState, dispatch }) => { const state = getState() as ApiState; const rootUri = params.uri || defaultRootUri; // 检查链接是否存在/过期 if (!state.links || Object.keys(state.links).length === 0) { await dispatch(fetchRootLinksAsync(rootUri)).unwrap(); } const latestState = getState() as ApiState; const link = latestState.links[relName]; if (!link) throw new Error(`未找到链接关系:${relName}`); let href = link.href; if (link.templated) { href = fillTemplateParameters(utpl(href), params.pageOptions, params.filterOptions); } const res = await axios.get(href, requestConfig); return res.data; } ) }; // 新增资源请求仅需一行代码 export const requestTasksAsync = createHateoasRequestThunk<APITasksResponse>('api/tasks', 'http://acme.com/rel/tasks');
补充说明
- 非函数组件需要dispatch action时,可以直接引入全局store实例,调用
store.dispatch()即可 - 如果需要处理链接过期逻辑,可以在state中给链接存储增加过期时间戳,检查依赖时同时校验有效性
- 所有链路的错误可以通过thunk的rejected状态统一处理,不需要在组件中做额外判断
内容的提问来源于stack exchange,提问作者Roman Vottner
相关产品推荐
相关产品推荐

