Redux如何设计actions与reducer实现同页面两API数据独立渲染
问题场景
单页面内包含2个图表组件,两个图表的数据源来自相互独立的API,但共用页面上的同一套UI筛选逻辑:用户在筛选区选定参数后,系统会携带相同的筛选查询参数分别向两个API发起请求,两个接口返回的各自数据需要分别渲染到对应图表中。
现存问题
现有实现中两个接口请求的actions复用了同一套ActionTypes,reducer仅维护单一data字段存储接口数据,导致后返回的接口数据会覆盖先返回的数据,从store中读取的图表数据会被异常篡改,无法实现两个图表数据的独立管理,同时需要完整保留原有的加载中、请求错误、超时等状态处理能力。
原有问题代码如下:
actions.js(原实现)
export const getSeverityDistData = (dispatch, urlqueryStr) => { handleFetch( { ...getSeverityDist(), urlqueryStr, }, // fetch params (res) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_SUCCESS, payload: { data: res.data, isLoading: false, onError: false, }, }); }, (e) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_ERROR, payload: { errorMsg: e.message, isLoading: false, }, }); }, (loading) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_LOADING, payload: loading, }); } );}; export const getGroupData = (dispatch, urlqueryStr) => { handleFetch( { ...getGruop(), urlqueryStr, },//...sameOperations SameActionTypes...}
reducers.js(原实现)
const setChartsData = (state, action) => ({ ...state, data: [...action.payload.data], isLoading: action.payload.isLoading, onError: action.payload.onError, }); const setLoading = (state, action) => ({ ...state, isLoading: action.payload, }); const setTimeOut = (state, action) => ({ ...state, isTimeout: action.payload, }); const onError = (state, action) => ({ ...state, onError: action.payload.errorMsg, isLoading: action.payload.isLoading, }); const initialState = { data: [], isLoading: false, isTimeout: false, onError: '', }; export default (state = initialState, action) => { switch (action.type) { case ActionTypes.GET_DASHBOARD_LIST_SUCCESS: return setChartsData(state, action); case ActionTypes.GET_DASHBOARD_LIST_LOADING: return setLoading(state, action); case ActionTypes.GET_DASHBOARD_LIST_TIME_OUT: return setTimeOut(state, action); case ActionTypes.GET_DASHBOARD_LIST_ERROR: return onError(state, action); default: return state; } };
实现方案
核心思路是按图表维度拆分独立状态切片,在action payload中增加唯一图表标识字段区分请求归属,无需完全重写原有逻辑,改动成本极低。
Action层改造
首先定义图表唯一标识常量,避免硬编码导致的匹配错误;给两个接口的所有dispatch动作都加上对应图表标识,原有ActionTypes可直接复用,无需新增重复的类型定义。
// 图表标识常量,后续新增图表直接在这里追加即可 export const CHART_KEYS = { SEVERITY_DIST: 'severityDist', GROUP: 'group' } export const getSeverityDistData = (dispatch, urlqueryStr) => { const chartKey = CHART_KEYS.SEVERITY_DIST handleFetch( { ...getSeverityDist(), urlqueryStr, }, (res) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_SUCCESS, payload: { chartKey, data: res.data, isLoading: false, onError: false, }, }); }, (e) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_ERROR, payload: { chartKey, errorMsg: e.message, isLoading: false, }, }); }, (loading) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_LOADING, payload: { chartKey, loading }, }); }, (timeout) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_TIME_OUT, payload: { chartKey, isTimeout: timeout } }) } ); }; export const getGroupData = (dispatch, urlqueryStr) => { const chartKey = CHART_KEYS.GROUP handleFetch( { ...getGruop(), urlqueryStr, }, (res) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_SUCCESS, payload: { chartKey, data: res.data, isLoading: false, onError: false, }, }); }, (e) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_ERROR, payload: { chartKey, errorMsg: e.message, isLoading: false, }, }); }, (loading) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_LOADING, payload: { chartKey, loading }, }); }, (timeout) => { dispatch({ type: ActionTypes.GET_DASHBOARD_LIST_TIME_OUT, payload: { chartKey, isTimeout: timeout } }) } ); };
Reducer层改造
将原有单一根状态调整为按chartKey存储独立状态的结构,每个图表对应独立的data、isLoading、isTimeout、onError字段,更新状态时仅修改对应chartKey下的属性,从根本上避免不同接口数据互相覆盖的问题。
import { CHART_KEYS } from './actions' // 单个图表的初始状态生成函数 const createChartInitialState = () => ({ data: [], isLoading: false, isTimeout: false, onError: '', }) const initialState = { [CHART_KEYS.SEVERITY_DIST]: createChartInitialState(), [CHART_KEYS.GROUP]: createChartInitialState(), }; export default (state = initialState, action) => { const { chartKey } = action.payload || {} // 无有效chartKey的动作直接返回原状态,兼容其他全局逻辑 if (!chartKey || !state[chartKey]) return state switch (action.type) { case ActionTypes.GET_DASHBOARD_LIST_SUCCESS: return { ...state, [chartKey]: { ...state[chartKey], data: [...action.payload.data], isLoading: action.payload.isLoading, onError: action.payload.onError, isTimeout: false } }; case ActionTypes.GET_DASHBOARD_LIST_LOADING: return { ...state, [chartKey]: { ...state[chartKey], isLoading: action.payload.loading, // 发起新请求时清空历史错误、超时状态 onError: '', isTimeout: false } }; case ActionTypes.GET_DASHBOARD_LIST_TIME_OUT: return { ...state, [chartKey]: { ...state[chartKey], isTimeout: action.payload.isTimeout, isLoading: false } }; case ActionTypes.GET_DASHBOARD_LIST_ERROR: return { ...state, [chartKey]: { ...state[chartKey], onError: action.payload.errorMsg, isLoading: action.payload.isLoading, isTimeout: false } }; default: return state; } };
组件侧适配
两个图表组件从store取数时,仅读取对应chartKey下的状态即可:
- 严重程度分布图表读取
state.dashboard[CHART_KEYS.SEVERITY_DIST]下的data、isLoading等状态 - 分组统计图表读取
state.dashboard[CHART_KEYS.GROUP]下的对应状态
方案优势
- 无需新增多套重复的ActionTypes,避免冗余代码
- 扩展性强,后续新增同页面其他图表时,仅需在
CHART_KEYS中新增对应常量、补充接口调用逻辑即可,无需修改reducer核心逻辑 - 每个图表独立维护加载、错误、超时状态,不会出现A接口请求完成就覆盖B接口数据的问题,也不会出现单个接口加载完成就关闭所有图表加载态的异常
- 原有筛选区触发请求的逻辑无需改动,筛选参数变更时并行调用两个数据请求action即可
内容的提问来源于stack exchange,提问作者Taha Yıldırım
相关产品推荐
相关产品推荐

