Redux 状态突变
Redux state mutation
为什么明明我传递的是一个新对象,但 React 却认为我正在改变状态?我错过了什么吗?
这是我在测验数组中的测验对象上添加问题的缩减器。
case types.UPDATE_QUESTION_SUCCESS: {
let quizContext = state.filter(quiz => quiz.id === action.quizId);
let filteredQuestions = quizContext[0].questions.filter(question =>
question.questionId !== action.question.questionId
);
filteredQuestions.push(action.question);
quizContext[0].questions = filteredQuestions;
let quiz = quizContext[0];
return [...state.filter(quiz => quiz.id !== action.quizId), Object.assign({}, quiz)];
}
错误:
Invariant Violation: A state mutation was detected between dispatches, in the path `quizes.4.questions.1`. This may cause incorrect behavior. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)
您正在根据您的减速器改变状态。当您使用 filter 函数时,传递给过滤器的对象是原始对象的引用,您不是在此处执行 deepCopy。而这一行 filterQuestions.push(action.question)
实际上是在改变状态。
试试这个:
let quizContext = cloneDeep(state.filter(quiz => quiz.id === action.quizId));
尽管可能有更优雅的解决方案,但这就是您出错的地方。返回的过滤对象是对原始数组的引用,而不是新对象。所以你最终改变了状态中的原始对象。
--更多说明--
cloneDeep
是一个 lodash 函数,如果你使用 Object.assign
它只会进行浅合并,而不是深度合并。因此,当状态在更深层次上发生变化时,通过改变原始状态,引用仍然是原始对象的引用
--编辑 2--
正如 mash 所指出的那样,改变你的状态的行是 quizContext[0].questions = filteredQuestions;
并使用 Object.assign
如果你确定你没有嵌套状态,在这种情况下更多的减速器是有序的
首先,既然你只关心quizContext[0]
,那么filter
就有点大材小用了。
只需做:
const quiz = state.find(quiz => quiz.id === action.quizId);
达到相同的结果。
我不想在这里做太多的代码审查,还有一件事。尽管您应该一直使用 const
,但您确实使用了 let
。 const
只是意味着你不能重新分配你仍然可以改变它的变量。
然后您过滤问题并添加一个,然后将其添加回您刚刚找到的测验。
quizContext[0].questions =
是问题所在,因为即使您的对象现在位于新数组中,它们仍然是相同的对象,因此如果您改变它们,您就会改变状态。
const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId));
所以重写完整的东西你会得到这样的东西:
case types.UPDATE_QUESTION_SUCCESS:
{
const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId));
quiz.questions = quiz.questions.filter(question =>
question.questionId !== action.question.questionId
);
quiz.questions.push(action.question);
return [...state.filter(quiz => quiz.id !== action.quizId), quiz];
}
但请记住,大多数时候您根本不想在 reducer 中过滤数据。相反,我建议使用 reselect 以便您可以从商店中的数据计算派生数据。
为什么明明我传递的是一个新对象,但 React 却认为我正在改变状态?我错过了什么吗? 这是我在测验数组中的测验对象上添加问题的缩减器。
case types.UPDATE_QUESTION_SUCCESS: {
let quizContext = state.filter(quiz => quiz.id === action.quizId);
let filteredQuestions = quizContext[0].questions.filter(question =>
question.questionId !== action.question.questionId
);
filteredQuestions.push(action.question);
quizContext[0].questions = filteredQuestions;
let quiz = quizContext[0];
return [...state.filter(quiz => quiz.id !== action.quizId), Object.assign({}, quiz)];
}
错误:
Invariant Violation: A state mutation was detected between dispatches, in the path `quizes.4.questions.1`. This may cause incorrect behavior. (http://redux.js.org/docs/Troubleshooting.html#never-mutate-reducer-arguments)
您正在根据您的减速器改变状态。当您使用 filter 函数时,传递给过滤器的对象是原始对象的引用,您不是在此处执行 deepCopy。而这一行 filterQuestions.push(action.question)
实际上是在改变状态。
试试这个:
let quizContext = cloneDeep(state.filter(quiz => quiz.id === action.quizId));
尽管可能有更优雅的解决方案,但这就是您出错的地方。返回的过滤对象是对原始数组的引用,而不是新对象。所以你最终改变了状态中的原始对象。
--更多说明--
cloneDeep
是一个 lodash 函数,如果你使用 Object.assign
它只会进行浅合并,而不是深度合并。因此,当状态在更深层次上发生变化时,通过改变原始状态,引用仍然是原始对象的引用
--编辑 2--
正如 mash 所指出的那样,改变你的状态的行是 quizContext[0].questions = filteredQuestions;
并使用 Object.assign
如果你确定你没有嵌套状态,在这种情况下更多的减速器是有序的
首先,既然你只关心quizContext[0]
,那么filter
就有点大材小用了。
只需做:
const quiz = state.find(quiz => quiz.id === action.quizId);
达到相同的结果。
我不想在这里做太多的代码审查,还有一件事。尽管您应该一直使用 const
,但您确实使用了 let
。 const
只是意味着你不能重新分配你仍然可以改变它的变量。
然后您过滤问题并添加一个,然后将其添加回您刚刚找到的测验。
quizContext[0].questions =
是问题所在,因为即使您的对象现在位于新数组中,它们仍然是相同的对象,因此如果您改变它们,您就会改变状态。
const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId));
所以重写完整的东西你会得到这样的东西:
case types.UPDATE_QUESTION_SUCCESS:
{
const quiz = Object.assign({}, state.find(quiz => quiz.id === action.quizId));
quiz.questions = quiz.questions.filter(question =>
question.questionId !== action.question.questionId
);
quiz.questions.push(action.question);
return [...state.filter(quiz => quiz.id !== action.quizId), quiz];
}
但请记住,大多数时候您根本不想在 reducer 中过滤数据。相反,我建议使用 reselect 以便您可以从商店中的数据计算派生数据。