在中间件中调度 redux 操作会导致意外行为
dispatching redux action in middleware cause unexpected behavior
我试图了解 redux 中间件的工作原理,在我的实验过程中,我注意到从 redux 中间件分派操作可能会导致意外行为。
我将尝试通过模拟文件上传来解释问题,如下所示:
我们有 3 个动作:
const setProgress = (progress) => ({ type: SET_UPLOAD_PROGRESS, progress });
const setThumbnail = (thumbnail) => ({ type: SET_THUMBNAIL, thumbnail });
const calculateTotal = () => ({ type: CALCULATE_TOTAL });
用于计算总数的中间件:
export const testMiddleware = (store) => (next) => (action) => {
if (action.type === 'CALCULATE_TOTAL') {
return next(action);
}
const result = next(action);
store.dispatch(calculateTotal());
return result;
};
减速器:
const initialState = {
progress: 0,
total: 0,
thumbnail: ''
};
export function uploadReducer(state = initialState, action) {
switch (action.type) {
case SET_UPLOAD_PROGRESS:
state.progress = action.progress;
return { ...state };
case SET_THUMBNAIL:
state.thumbnail = action.thumbnail;
return { ...state };
case CALCULATE_TOTAL:
state.total += state.progress * 5;
return { ...state };
default:
return state;
}
}
这里是模拟文件上传的代码:
let cnt = 0;
// simulate upload progress
const setNewProgress = () => {
cnt += 2;
if (cnt > 5) return;
setTimeout(() => {
store.dispatch(setProgress(cnt * 2));
setNewProgress();
}, 1000);
};
setNewProgress();
// simulate thumbnail generating
setTimeout(() => {
store.dispatch(setThumbnail('blob:http://thumbnail.jpg'));
}, 2500);
事件顺序如下:
第一个操作按预期工作并设置进度值:
问题从这里开始;缩略图应该由 'setThumbnail' 设置,但 devtools 显示它已由 'calculateTotal' 设置,并且之后的每个调度都不匹配:
我在这里做错了什么?是设计使然吗?如何在不引起上述问题的情况下在中间件中发送操作?
这种意外行为可能是由于您的 uploadReducer
不是 pure, i.e. it is directly operating on your state (e.g. state.progress = action.progress;
). Reducers should 仅 return 新状态,而不是修改通过 redux 注入到您的 reducer 中的现有状态。因此,您的减速器需要如下所示:
export function uploadReducer(state = initialState, action) {
switch (action.type) {
case SET_UPLOAD_PROGRESS:
return { ...state, progress: action.progress };
case SET_THUMBNAIL:
return { ...state, thumbnail: action.thumbnail };
case CALCULATE_TOTAL:
return { ...state, total: state.total + state.progress * 5 };
default:
return state;
}
}
how can I dispatch an action in middleware without causing above problem?
你的中间件看起来不错(你正确地防止了递归并且 returning next()
结果(这在你的例子中不需要,但在实际应用程序中仍然有意义)。你的行动看起来也不错(风格评论:您可以将操作的有效负载包装在 payload
属性 中,这是常见的 convention)。
我试图了解 redux 中间件的工作原理,在我的实验过程中,我注意到从 redux 中间件分派操作可能会导致意外行为。
我将尝试通过模拟文件上传来解释问题,如下所示:
我们有 3 个动作:
const setProgress = (progress) => ({ type: SET_UPLOAD_PROGRESS, progress });
const setThumbnail = (thumbnail) => ({ type: SET_THUMBNAIL, thumbnail });
const calculateTotal = () => ({ type: CALCULATE_TOTAL });
用于计算总数的中间件:
export const testMiddleware = (store) => (next) => (action) => {
if (action.type === 'CALCULATE_TOTAL') {
return next(action);
}
const result = next(action);
store.dispatch(calculateTotal());
return result;
};
减速器:
const initialState = {
progress: 0,
total: 0,
thumbnail: ''
};
export function uploadReducer(state = initialState, action) {
switch (action.type) {
case SET_UPLOAD_PROGRESS:
state.progress = action.progress;
return { ...state };
case SET_THUMBNAIL:
state.thumbnail = action.thumbnail;
return { ...state };
case CALCULATE_TOTAL:
state.total += state.progress * 5;
return { ...state };
default:
return state;
}
}
这里是模拟文件上传的代码:
let cnt = 0;
// simulate upload progress
const setNewProgress = () => {
cnt += 2;
if (cnt > 5) return;
setTimeout(() => {
store.dispatch(setProgress(cnt * 2));
setNewProgress();
}, 1000);
};
setNewProgress();
// simulate thumbnail generating
setTimeout(() => {
store.dispatch(setThumbnail('blob:http://thumbnail.jpg'));
}, 2500);
事件顺序如下:
第一个操作按预期工作并设置进度值:
问题从这里开始;缩略图应该由 'setThumbnail' 设置,但 devtools 显示它已由 'calculateTotal' 设置,并且之后的每个调度都不匹配:
我在这里做错了什么?是设计使然吗?如何在不引起上述问题的情况下在中间件中发送操作?
这种意外行为可能是由于您的 uploadReducer
不是 pure, i.e. it is directly operating on your state (e.g. state.progress = action.progress;
). Reducers should 仅 return 新状态,而不是修改通过 redux 注入到您的 reducer 中的现有状态。因此,您的减速器需要如下所示:
export function uploadReducer(state = initialState, action) {
switch (action.type) {
case SET_UPLOAD_PROGRESS:
return { ...state, progress: action.progress };
case SET_THUMBNAIL:
return { ...state, thumbnail: action.thumbnail };
case CALCULATE_TOTAL:
return { ...state, total: state.total + state.progress * 5 };
default:
return state;
}
}
how can I dispatch an action in middleware without causing above problem?
你的中间件看起来不错(你正确地防止了递归并且 returning next()
结果(这在你的例子中不需要,但在实际应用程序中仍然有意义)。你的行动看起来也不错(风格评论:您可以将操作的有效负载包装在 payload
属性 中,这是常见的 convention)。